On this page
Steer by wire
using SimpleFOCShield and Arduino UNO
Download the STL file as well as STEP and solidworks project of the 3d printed stand for the IPower GM4108H-120T motor with the mount for the CUI amt103 encoder, used in the images and the Youtube video, here.
Connecting everything together
Here is a picture of the setup used in this project. Check the Arduino code to see the pin numbers that are used.
Arduino code
The code for this example is very simple. Since we use two BLDC motors we will need to use all 6 pwm Arduino UNO pins which means that we do not have enough hardware interrupt pins. Therefore we will need to use software interrupts. In this example, we will use PciManager
library. It is very simple to use and I definitely recommend it, but it can be any other respectively.
Both motors will be controlled in the torque/voltage control mode and one motor will have encoder as a sensor, the other will be using the magnetic sensor with I2C communication (AS5600). After both motors and sensors are initialized, no configuration is necessary, we can initialize FOC algorithm for both motors initFOC()
and we are ready to go to real-time execution.
The smart stuff is placed in the loop()
function. 😄
In order to maintain the virtual link in between two motors positions we write the code:
motor1.move( 5*(motor2.shaft_angle - motor1.shaft_angle));
motor2.move( 5*(motor1.shaft_angle - motor2.shaft_angle));
This control algorithm sets the voltage to the motors, proportional to their distance from the position of the other motor. Constant 5
is a scaling gain, it is found empirically and it will differ for each setup. But in general, the higher the number the stiffer the link, it will the more hard to introduce an offset in between motors positions.
And that is it, here is the full code of this example.
#include <SimpleFOC.h>
// software interrupt library
#include <PciManager.h>
#include <PciListenerImp.h>
BLDCMotor motor1 = BLDCMotor(11);
BLDCDriver3PWM driver1 = BLDCDriver3PWM(3, 10, 6, 7);
Encoder encoder1 = Encoder(A2, 2, 500, A0);
void doA1(){encoder1.handleA();}
void doB1(){encoder1.handleB();}
void doI1(){encoder1.handleIndex();}
// encoder interrupt init
PciListenerImp listenerA(encoder1.pinA, doA1);
PciListenerImp listenerB(encoder1.pinB, doB1);
PciListenerImp listenerI(encoder1.index_pin, doI1);
BLDCMotor motor2 = BLDCMotor( 11);
BLDCDriver3PWM driver2 = BLDCDriver3PWM(9, 11, 5, 8);
MagneticSensorI2C sensor2 = MagneticSensorI2C(0x36, 12, 0x0E, 4);
void setup() {
// initialise encoder hardware
encoder1.init();
// interrupt initialization
PciManager.registerListener(&listenerA);
PciManager.registerListener(&listenerB);
PciManager.registerListener(&listenerI);
// link the motor to the sensor
motor1.linkSensor(&encoder1);
// init driver
driver1.init();
// link driver
motor1.linkDriver(&driver1);
// set control loop type to be used
motor1.controller = MotionControlType::torque;
// initialise motor
motor1.init();
// align encoder and start FOC
motor1.initFOC();
// initialise magnetic sensor hardware
sensor2.init();
// link the motor to the sensor
motor2.linkSensor(&sensor2);
// init driver
driver2.init();
// link driver
motor2.linkDriver(&driver2);
// set control loop type to be used
motor2.controller = MotionControlType::torque;
// initialise motor
motor2.init();
// align encoder and start FOC
motor2.initFOC();
Serial.println("Steer by wire ready!");
_delay(1000);
}
void loop() {
// iterative setting FOC phase voltage
motor1.loopFOC();
motor2.loopFOC();
// virtual link code
motor1.move( 5*(motor2.shaft_angle - motor1.shaft_angle));
motor2.move( 5*(motor1.shaft_angle - motor2.shaft_angle));
}
Haptic velocity control
using SimpleFOCShield and Stm32 Nucleo-64
Connecting everything together
Here is a picture of the setup used in this project. Check the Arduino code to see the pin numbers that are used.
Arduino code
In this example we will be using the Nucleo-64 board and two BLDC motors with encoders. Since Nucleo doesn’t have problems with hardware interrupts (every pin can be external interrupt pin), there is no complications in code due to usage of 6 pwm pins. The only difference in between Nucleo and Arduino UNO is that Nucleo pin cannot use its pin 11
for PWM so we need to use pin 13
instead. The rest of the code is very straight forward.
We define two motors and two encoders and link them together.
#include <SimpleFOC.h>
BLDCMotor motor1 = BLDCMotor(11);
BLDCDriver3PWM driver1 = BLDCDriver3PWM(9, 6, 5, 7);
Encoder encoder1 = Encoder(A1, A2, 8192);
void doA1(){encoder1.handleA();}
void doB1(){encoder1.handleB();}
BLDCMotor motor2 = BLDCMotor(11);
BLDCDriver3PWM driver2 = BLDCDriver3PWM(3, 13, 10, 8);
Encoder encoder2 = Encoder(A3, 2, 500);
void doA2(){encoder2.handleA();}
void doB2(){encoder2.handleB();}
void setup() {
// initialise encoder hardware
encoder1.init();
encoder1.enableInterrupts(doA1,doB1);
encoder2.init();
encoder2.enableInterrupts(doA2,doB2);
// link the motor to the sensor
motor1.linkSensor(&encoder1);
motor2.linkSensor(&encoder2);
// config drivers
driver1.init();
driver2.init();
// link the motor to the driver
motor1.linkDriver(&driver1);
motor2.linkDriver(&driver2);
}
void loop(){}
Then we define the motion control to be voltage for one motor and the velocity for the other:
// set control loop type to be used
motor1.controller = MotionControlType::torque;
motor2.controller = MotionControlType::velocity;
Additionally we introduce a bit higher degree of filtering by augmenting Tf
value and rise a bit the integral gain I
as well for better following.
// augment filtering
motor2.LPF_velocity.Tf = 0.02;
// rise I gain
motor2.PID_velocity.I = 40;
And to finish the setup()
we just initialize the motors and FOC algorithm.
The smart stuff is again in the the loop()
function. The virtual link code looks like this:
// virtual link code
motor1.move(5*(motor2.shaft_velocity/10 - motor1.shaft_angle));
motor2.move(10*motor1.shaft_angle);
This control strategy basically says that the target velocity of the motor2
will be proportional to the position of the motor1
. On the other hand, it sets the voltage to the motor1
proportional to the difference in between the difference on the velocity of the motor2
and position of the motor1
. This creates a virtual link in between those two variables.
Constant 5
is the scaling gain with the same role as in the previous example. It will just make the motor1
be more or less responsive while following the motor2
velocity and it will want to maintain the difference in 0.
Constant 10
is a bit different. It is a scaling factor which helps to better map velocity to the positon. For example in the example we are using the motor2
has maximal velocity of 60rad/s
, but we don’t want our gauge to rotate 10 rotations to show this velocity. we would like it to rotate maximally 1 rotation ~6rad
therefore the constant 10
. But in your case maybe you will be running a drone motor, which turns with thousands of RPM and you might want to have even larger scaling 100 or even a 1000. On the other hand, maybe you will want to have a very precise slow moving motor which will go under 1 radian/s. And you will want to go to the values of ~0.1
or even less. Therefore this will depend on your application and the precisions you will need to have.
One more interesting thing to notice is that this scaling factor can be variable, so you could change it in real-time as well.
Here is the full example code:
#include <SimpleFOC.h>
BLDCMotor motor1 = BLDCMotor(11);
BLDCDriver3PWM driver1 = BLDCDriver3PWM(9, 6, 5, 7);
Encoder encoder1 = Encoder(A1, A2, 8192);
void doA1(){encoder1.handleA();}
void doB1(){encoder1.handleB();}
BLDCMotor motor2 = BLDCMotor(11);
BLDCDriver3PWM driver2 = BLDCDriver3PWM(3, 13, 10, 8);
Encoder encoder2 = Encoder(A3, 2, 500);
void doA2(){encoder2.handleA();}
void doB2(){encoder2.handleB();}
void setup() {
// initialise encoder hardware
encoder1.init();
encoder1.enableInterrupts(doA1,doB1);
encoder2.init();
encoder2.enableInterrupts(doA2,doB2);
// link the motor to the sensor
motor1.linkSensor(&encoder1);
motor2.linkSensor(&encoder2);
// config drivers
driver1.init();
driver2.init();
// link the motor to the driver
motor1.linkDriver(&driver1);
motor2.linkDriver(&driver2);
// set control loop type to be used
motor1.controller = MotionControlType::torque;
motor2.controller = MotionControlType::velocity;
motor2.LPF_velocity.Tf = 0.02;
motor2.PID_velocity.I = 40;
// use monitoring with serial for motor init
// monitoring port
Serial.begin(115200);
// enable monitoring
motor1.useMonitoring(Serial);
motor2.useMonitoring(Serial);
// initialise motor
motor1.init();
motor2.init();
// align encoder and start FOC
motor1.initFOC();
motor2.initFOC();
Serial.println("Interactive gauge ready!");
_delay(1000);
}
void loop() {
// iterative setting FOC phase voltage
motor1.loopFOC();
motor2.loopFOC();
// iterative function setting the outter loop target
motor1.move(5*(motor2.shaft_velocity/10 - motor1.shaft_angle));
motor2.move(10*dead_zone(motor1.shaft_angle));
}
float dead_zone(float x){
return abs(x) < 0.2 ? 0 : x;
}