On this page
- Let’s get started
Let’s get started
Once you have your SimpleFOClibrary installed and you have all the necessary hardware, we can finally start the fun part, let’s write the code and move the motor!
Step 1. Testing the sensor
The first sign that tells us that everything is well connected is if the sensor readings are good. To test the sensor please browse the library examples examples/utils/sensor_test
and find the example for your sensor. The example will have a structure something like this:
Encoder Magnetic sensor SPI Magnetic sensor I2C Magnetic sensor Analog Hall sensors
#include <SimpleFOC.h>
Encoder encoder = Encoder(2, 3, 500);
// interrupt routine initialisation
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}
void setup() {
// monitoring port
Serial.begin(115200);
// initialise encoder hardware
encoder.init();
// hardware interrupt enable
encoder.enableInterrupts(doA, doB);
Serial.println("Encoder ready");
_delay(1000);
}
void loop() {
// IMPORTANT
// read sensor and update the internal variables
encoder.update();
// display the angle and the angular velocity to the terminal
Serial.print(encoder.getAngle());
Serial.print("\t");
Serial.println(sensor.getVelocity());
}
Make sure to change the sensor parameters to suit your application, such as pin numbers, impulses per revolution, bus addresses and similar. Make sure to go through the sensor docs if you are not sure about some of the parameters.
If your sensor is well connected and if everything works well, you should have in your serial terminal an output of your sensor angle and velocity.
☑️ Simple test
Make sure to test that one rotation of the motor gives you the output of 6.28(\(2\pi\))
RAD
.
If you’re new to radiansRAD
, you can find more info about the library units in the here.
Step 2. Testing just the driver
Once your sensor is working you can proceed to the driver test. The simplest way to test the driver is to use the library examples. If you have the luxury of time you can test the driver using the examples in the examples/utils/driver_standalone_test
folder. These examples test the driver as a standalone module and with them you can set any voltage value to any of the driver phases.
3PMW BLDC Driver 6PMW BLDC Driver 2PWM Stepper Driver 4PWM Stepper Driver
#include <SimpleFOC.h>
// BLDCDriver3PWM(pwmA, pwmB, pwmC, (en optional))
BLDCDriver3PWM driver = BLDCDriver3PWM(9, 5, 6, 8);
void setup() {
// use monitoring with serial
Serial.begin(115200);
// enable more verbose output for debugging
// comment out if not needed
SimpleFOCDebug::enable(&Serial);
// power supply voltage [V]
driver.voltage_power_supply = 12;
// Max DC voltage allowed - default voltage_power_supply
driver.voltage_limit = 12;
// driver init
if (!driver.init()){
Serial.println("Driver init failed!");
return;
}
// enable driver
driver.enable();
Serial.println("Driver ready!");
_delay(1000);
}
void loop() {
// setting pwm
// phase A: 3V
// phase B: 6V
// phase C: 5V
driver.setPwm(3,6,5);
}
Once you have the code ready, upload it to your board and open the serial terminal. If everything is connected correctly your PWM driver should be ready and you should see the Driver ready!
message in the serial terminal. If the driver does not initialise correctly, please check the following things:
- Make sure to change the driver parameters to suit your application, such as pin numbers, PWM frequency, power supply voltage and similar. Make sure to go through the driver docs if you are not sure about some of the parameters.
- Make sure you use the right driver class and right pwm pins, this one is usually very simple and you can find a lot of docs about these things in our driver docs and in our guide to choosing the PWM pins. If you are not sure and cannot seem to make it work please do not hesitate to ask in our community forum.
☑️ Simple tests
- Open your serial monitor and verify that the
Driver ready!
has been displayed. If theDriver init failed!
has been displayed, check the debugging output for more information what went wrong.- Make sure that all phases output PWM signals, you can try to connect a small led light in between each phase and ground or just measure it with the multimeter.
Step 2. Testing the driver + motor combination - open-loop
If you already have your motor connected and if you are sure your driver works well we advise you to test the motor+driver combination using the Open-Loop control examples in the examples/motion_control/open_loop_motion_control
. If your driver is not the same as the ones provided in the examples, go though the driver docs and find the driver and the code that will work for you. Additionally you can browse through the examples in the examples/utils/driver_standalone_test
folder and see the used in there.
Please Note
Before you try this example, please take at least a short look at the rules just below the code. From here on, actual current is flowing through your electronics and your driver has no way of measuring it (at least not in this example).
So the main aim of this step is to test the motor parameters, such as the number of pole pairs, and verify that the driver is capable of driving the motor.
BLDC Motor + 3PMW driver BLDC Motor + 6PMW driver Stepper Motor + 2PMW driver Stepper Motor + 4PMW driver
Now, here is an example of the open-loop velocity control for the BLDC motor using the 3PWM driver.
#include <SimpleFOC.h>
// BLDCMotor(pole pair number, phase resistance (optional) );
BLDCMotor motor = BLDCMotor(11);
// BLDCDriver3PWM(pwmA, pwmB, pwmC, Enable(optional));
BLDCDriver3PWM driver = BLDCDriver3PWM(9, 5, 6, 8);
// instantiate the commander
Commander command = Commander(Serial);
void doTarget(char* cmd) { command.scalar(&motor.target, cmd); }
void doLimit(char* cmd) { command.scalar(&motor.voltage_limit, cmd); }
void setup() {
// use monitoring with serial
Serial.begin(115200);
// enable more verbose output for debugging
// comment out if not needed
SimpleFOCDebug::enable(&Serial);
// driver config
// power supply voltage [V]
driver.voltage_power_supply = 12;
// limit the maximal dc voltage the driver can set
// as a protection measure for the low-resistance motors
// this value is fixed on startup
driver.voltage_limit = 6;
if(!driver.init()){
Serial.println("Driver init failed!");
return;
}
// link the motor and the driver
motor.linkDriver(&driver);
// limiting motor movements
// limit the voltage to be set to the motor
// start very low for high resistance motors
// current = voltage / resistance, so try to be well under 1Amp
motor.voltage_limit = 3; // [V]
// open loop control config
motor.controller = MotionControlType::velocity_openloop;
// init motor hardware
if(!motor.init()){
Serial.println("Motor init failed!");
return;
}
// set the target velocity [rad/s]
motor.target = 6.28; // one rotation per second
// add target command T
command.add('T', doTarget, "target velocity");
command.add('L', doLimit, "voltage limit");
Serial.println("Motor ready!");
Serial.println("Set target velocity [rad/s]");
_delay(1000);
}
void loop() {
// open loop velocity movement
motor.move();
// user communication
command.run();
}
Once you have the code ready, upload it to your board and open the serial terminal. If everything is connected correctly you should see the motor spinning. If the motor does not spin, please check the following things:
- Make sure you use the right driver class and right pwm pins, this one is usually very simple and you can find a lot of docs about these things in our driver docs and in our guide to choosing the PWM pins.
- Make sure to use a proper voltage limit. All of these examples assume you are using a gimbal type motor with high phase resistance (~10 Ohms). For higher power motors, the current will probably be too high, putting your motor+driver at risk. As a rule of thumb, if your motor has a servo type plug, you should be fine.
motor.voltage_limit
will directly determine the current passing through the motor:current = phase_resistance*motor.voltage_limit
. So to avoid too high currents please try to find what is the phase resistance of your motor and set themotor.voltage_limit
such that the current does not surpass 2 Amps for example:motor.voltage_limit = 2*phase_resistance
. The best option would be to provide your phase resistance value to the motor by setting the parametermotor.phase_resistance
and then you can usemotor.current_limit
instead of themotor.voltage_limit
making this issue much simpler. If you wanna try to measure the phase resistance of your motor you can find the our guide here. If you cannot find the phase resistance of your motor and cannot measure it with your multimeter start small:motor.voltage_limit < 1;
. - Make sure to put the right pole pair number. This you can find in most data-sheets, if you are not sure what is the true number don’t worry this step is intended to test/find exactly this value. 😄
☑️ Simple tests
- Verify in your serial terminal that the motor and the driver have been initialised without any issues. That you can see
Motor ready!
and not seeMotor init failed!
orDriver init failed!
.- In velocity mode: (
motor.controller = MotionControlType::velocity_openloop;
)
set your target velocity (T6.28
) to 6.28 (\(2\pi\)) rad/s (typeT6.28
), this should be exactly one rotation per second.- In position mode, (
motor.controller = MotionControlType::angle_openloop;
)
set the target position to 6.28 (\(2\pi\)) rad (typeT6.28
), it should be exactly one rotation.If your motor does not spin exactly one rotation per second in velocity mode or does not do exactly one rotation in angle mode, this probably means that your pole pairs number is not good. Try changing it until you get exactly one rotation (or one rotation per second in velocity mode)
Step 3. Closed-loop control - torque using voltage
Once you have a working sensor, working motor and driver you can proceed to the Closed-Loop control testing. The first one to test is the torque control mode using voltage, this one is the simplest form of the closed loop control that is available in the SimpleFOClibrary. You can find the examples of this torque mode for different sensors in the library examples folder examples/motion_control/torque_control
.
Sketch template BLDC Motor + 3PMW driver + Encoder Stepper Motor + 2PMW driver + Encoder
Here is an example sketch with the TODO
entries that you can implement for your motor, driver and sensor.
#include <SimpleFOC.h>
// TODO: motor instance
// TODO: driver instance
// TODO: sensor instance
// instantiate the commander
Commander command = Commander(Serial);
void doMotor(char* cmd) { command.motor(&motor, cmd); }
void setup() {
// use monitoring with serial
Serial.begin(115200);
// enable more verbose output for debugging
// comment out if not needed
SimpleFOCDebug::enable(&Serial);
// TODO: driver config
// init the sensor
sensor.init()
// link the motor to the sensor
motor.linkSensor(&sensor);
// TODO: driver config
// init the driver
if(!driver.init()){
Serial.println("Driver init failed!");
return;
}
// link driver
motor.linkDriver(&driver);
// TODO: motor conf
// set motion control loop to be used
motor.torque_controller = TorqueControlType::voltage;
motor.controller = MotionControlType::torque;
// use monitoring with serial
// comment out if not needed
motor.useMonitoring(Serial);
// initialize motor
if(!motor.init()){
Serial.println("Motor init failed!");
return;
}
// align sensor and start FOC
if(!motor.initFOC()){
Serial.println("FOC init failed!");
return;
}
// set the initial motor target
motor.target = 2; // Volts
// add target command M
command.add('M', doMotor, "Motor");
Serial.println(F("Motor ready."));
Serial.println(F("Set the target using serial terminal and command M:"));
_delay(1000);
}
void loop() {
// main FOC algorithm function
motor.loopFOC();
// Motion control function
motor.move();
// user communication
command.run();
}
Until now, you already know the good configuration of your sensor, your driver and your motor. If everything worked until this point you most probably will not have any issues with this step as well. You can adapt the above examples to your setup and upload the code to your board.
☑️ Simple test
- If everything is connected correctly you should see the motor spinning and you should be able to set the target voltage through the serial terminal (command
M
).- If the motor is not spinning please verify that the motor and driver have been initialised without any issues by checking the serial terminal output (make sure that you can see
Motor ready!
and not seeMotor init failed!
orDriver init failed!
).
The most common issues that might arise are:
- The motor does not spin at all
- The driver is probably not initialised correctly, check the serial terminal output for any errors.
- The motor spins during the
motor.initFOC()
function but it does not spin afterwards- Verify the output of the
motor.initFOC()
function, there is probably an error with the sensor alignment or pole pairs number.
- Verify the output of the
- The motor spins but is unstable and makes a lot of noise
- Verify the output of the
motor.initFOC()
function- Check the pole pair check and if it failed it probably means that the pole pairs number is not correct, try to change it.
- If everything seams OK in the serial terminal the sensor is probably not aligned correctly, try to rise/lower the
motor.voltage_sensor_align
value.
- Verify the output of the
Please Note
Make sure you use not too high
motor.voltage_sensor_align
value to prevent too high currents. The rule is the same as for themotor.voltage_limit
for the open loop. If you are not sure what is your phase resistance, start small:motor.voltage_sensor_align < 1
. Additionally, you can define the motor phase resistance as well and then use themotor.current_limit
variable, this variable will limit themotor.voltage_sensor_align
and you will not have to worry about it any more. But if you specify the phase resistance value you will no longer be setting voltage command to the motor but current command, see torque control docs for more info.
Step 3.1. Velocity and angle control
Once the above code works well, you can proceed to the velocity and angle motion control.
- To go from the torque control to the velocity control you just need to change the
motor.controller
variable toMotionControlType::velocity
and set themotor.target
to the desired velocity (in radians per secondRAD/s
). - To go from the torque control to the angle control you just need to change the
motor.controller
variable toMotionControlType::angle
and set themotor.target
to the desired angle (in radiansRAD
). - Another way of transitioning to the velocity control is using the serial terminal through the
Commander
interface- To start the velocity control mode type
MC1
in the serial terminal and press enter.- To set the target velocity type
M6.28
in the serial terminal and press enter. This will set the target velocity to one rotation per second. - To stop the motor type
M0
in the serial terminal and press enter. This will set the target velocity to zero.
- To set the target velocity type
- To start the angle control mode type
MC2
in the serial terminal and press enter.- To set the target angle type
M0
in the serial terminal and press enter. This will set the target angle to one rotation. - Once your motor is at the
0
angle, try setting the target angle toM6.28
in the serial terminal and press enter. This will set the target angle to one rotation.
- To set the target angle type
- To go back to the torque control mode type
MC0
in the serial terminal and press enter. - See more about the
Commander
interface in the Commander docs.
- To start the velocity control mode type
The velocity and angle motion control modes have additional configuration parameters that might be needed to obtain a smooth and stable operation of your motor. You can find more info about these parameters in the motion control docs.
Step 4. Testing the current sense - if available
If your setup has current sensing that is supported by the SimpleFOClibrary then we would suggest you to still make sure that you can run at least closed-loop torque control using voltage (Step 3.) before doing this step.
The best way to start is to add the current sensing to the code of the torque control using voltage (Step 3.) and output the d and q currents to the serial terminal using monitoring.
Sketch template BLDC Motor + 3PMW driver + Encoder + Inline Current Sensing Stepper Motor + 2PMW driver + Encoder + Inline Current Sensing
Here is an example sketch with the TODO
entries that you can implement for your motor, driver, current sense and sensor.
#include <SimpleFOC.h>
// TODO: motor instance
// TODO: driver instance
// TODO: sensor instance
// TODO: current sense instance
// instantiate the commander
Commander command = Commander(Serial);
void doMotor(char* cmd) { command.motor(&motor, cmd); }
void setup() {
// use monitoring with serial
Serial.begin(115200);
// enable more verbose output for debugging
// comment out if not needed
SimpleFOCDebug::enable(&Serial);
// TODO: driver config
// init the sensor
sensor.init()
// link the motor to the sensor
motor.linkSensor(&sensor);
// TODO: driver config
// init the driver
if(!driver.init()){
Serial.println("Driver init failed!");
return;
}
// link driver
motor.linkDriver(&driver);
// link driver to cs
current_sense.linkDriver(&driver);
// TODO: current sense config
// current sense init
if(!current_sense.init()){
Serial.println("Current sense init failed!");
return;
}
// link the current sense to the motor
motor.linkCurrentSense(¤t_sense);
// TODO: motor conf
// set motion control loop to be used
motor.torque_controller = TorqueControlType::voltage;
motor.controller = MotionControlType::torque;
// use monitoring with serial
// comment out if not needed
motor.useMonitoring(Serial);
motor.monitor_downsampling = 100; // set downsampling can be even more > 100
motor.monitor_variables = _MON_CURR_Q | _MON_CURR_D; // set monitoring of d and q currents
// initialize motor
if(!motor.init()){
Serial.println("Motor init failed!");
return;
}
// align sensor and start FOC
if(!motor.initFOC()){
Serial.println("FOC init failed!");
return;
}
// set the initial motor target
motor.target = 2; // Volts
// add target command M
command.add('M', doMotor, "Motor");
Serial.println(F("Motor ready."));
Serial.println(F("Set the target using serial terminal and command M:"));
_delay(1000);
}
void loop() {
// main FOC algorithm function
motor.loopFOC();
// Motion control function
motor.move();
// display the currents
motor.monitor();
// user communication
command.run();
}
This example code is almost exactly the same as the Step 3. code so you should not have much trouble configuring motor, sensor and driver. In this step you will be testing if your current sense is working well. In the call of the motor.monitor()
function the current sense will be read and the current d and q are going to be printed to the serial terminal. You can open the Serial Plotter to visualise them.
☑️ Simple test
- If everything is connected correctly you should see the motor spinning and you should be able to set the target voltage through the serial terminal (command
M
).- Hold the motor with your hand and set different target voltage/current values. Make sure that the current d is very close to 0 when the motor is static. And make sure that the current q is proportional to the voltage you are setting to the motor.
- Leave the motor to rotate. See that your currents d and q drop to a lower level than for static motor. Also try to see that the current d is almost 0 for low velocities and starts rising proportionally to the motor velocity.
If the motor is not spinning please verify that the motor and driver have been initialised without any issues by checking the serial terminal output (make sure that you can see Motor ready!
and not see Motor init failed!
, Driver init failed!
or Current sense init failed!
). Our debugging interface will output a lot of information during the driver, current sense and motor init that can help you debug potential issues with your setup. If you are having issues with the current sense please go through the current sense docs to see the supported sensors and all the configuration parameters.
The most common issues that might arise are:
- Current sense pins are not ADC pins - see our guide to choosing ADC pins for more info.
- The driver is not linked to the current sense - make sure you have the
current_sense.linkDriver(&driver);
line in your code. - PWM timer and ADC cannot be synchronised - see our guide to choosing PWM pins for more info.
- Current sense alignment has failed - verify the debugging output of the
motor.initFOC()
function and try to change themotor.voltage_sensor_align
value.
Step 5. Full FOC motion control using current sensing - if available
Once you have your motor, position sensor, driver and current sense configured and tested you can now proceed to try out true field oriented control (FOC).
- To go from the torque control using voltage to the torque control using current you just need to change the
motor.torque_controller
variable toTorqueControlType::foc_current
and set themotor.controller
variable toMotionControlType::torque
. - Another way of transitioning to the current control is using the serial terminal through the
Commander
interface- To start the current control mode type
MT2
in the serial terminal and press enter.- To set the target current type
M0.5
(in amperes) in the serial terminal and press enter. This will set the target current to 0.5 Amps. - To stop the motor type
M0
in the serial terminal and press enter. This will set the target current to zero.
- To set the target current type
- To go back to the voltage control mode type
MT0
in the serial terminal and press enter. - See more about the
Commander
interface in the Commander docs.
- To start the current control mode type
☑️ Simple test
1. Set your target current to 0Amps and try to move the motor by hand, make sure it feels like there is absolutely no resistance, like the motor is disabled. 2. Then try to set a small current (<0.5A) value and see if you can feel the motor force acting on your hand. If you can feel it you should be ready to go!
The current control mode has additional configuration parameters that might be needed to obtain a smooth and stable operation of your motor. You can find more info about these parameters in the torque control docs.
However the most important tip is still to use the commander interface to tune the torque control PID controller and Low pass filter parameters. This way you will be able to change and test the parameters of controllers in real-time and see what will happen. Once you are satisfied, you can write these values into your code and quit using the commander.
Step 5.1. Velocity and angle control
Once the current control works well you can proceed to the velocity and angle motion control.
- To go from the current control to the velocity control you just need to change the
motor.controller
variable toMotionControlType::velocity
and set themotor.target
to the desired velocity (in radians per secondRAD/s
). - To go from the current control to the angle control you just need to change the
motor.controller
variable toMotionControlType::angle
and set themotor.target
to the desired angle (in radiansRAD
). - You can also use the
Commander
interface to transition to the velocity and angle control modes using theMC1
andMC2
commands.
The velocity and angle motion control modes have additional configuration parameters that might be needed to obtain a smooth and stable operation of your motor. You can find more info about these parameters in the motion control docs.