Getting started with SimpleFOC and ODriveBoard

Hi everyone,

I’m starting to use SimpleFOC together with an ODrive board with a BLDC motor and an optical encoder.

Note that I’m brand new with SimpleFOC (but pretty experienced in programming, including all kind of microcontroller boards).

I got the code skeleton from the examples and this forum.

I checked the encoder standalone, which works fine. Then I checked the driver standalone, which works fine.

So far, so good.

Then I started with the very simple velocity_openloop mode, which works also fine: the motor rotate in the right direction, with the appropriate speed.

But I don’t succeed to do anything else. The velocity control (ie not openloop) does not work at all.

I tried to calibrate the PID’s as explained in this forum, without any success.

I digged a bit using the great simpleFOC-GUI, but no luck.

Any advice ?

My code :

#include <Arduino.h>

#include <SimpleFOC.h>
#include <SimpleFOCDrivers.h>

// https://github.com/simplefoc/Arduino-FOC/blob/master/examples/hardware_specific_examples/Odrive_examples/odrive_example_spi/odrive_example_spi.ino

// Odrive M0 motor pinout
#define M0_INH_A PA8
#define M0_INH_B PA9
#define M0_INH_C PA10
#define M0_INL_A PB13
#define M0_INL_B PB14
#define M0_INL_C PB15
// M0 currents
#define M0_IB PC0
#define M0_IC PC1
// Odrive M0 encoder pinout
#define M0_ENC_A PB4
#define M0_ENC_B PB5
#define M0_ENC_Z PC9

// M1 & M2 common enable pin
#define EN_GATE PB12

// SPI pinout
#define SPI3_SCL PC10
#define SPI3_MISO PC11
#define SPI3_MOSO PC12

Encoder encoder = Encoder(M0_ENC_A, M0_ENC_B, 5120, M0_ENC_Z);

/**
     BLDCMotor class constructor
     @param pp pole pairs number
     @param R  motor phase resistance - [Ohm]
     @param KV  motor KV rating (1/K_bemf) - rpm/V
     @param L  motor phase inductance - [H]

    BLDCMotor(int pp,  float R = NOT_SET, float KV = NOT_SET, float L = NOT_SET);
*/
BLDCMotor motor = BLDCMotor(14, NOT_SET , 600., NOT_SET);
// BLDCMotor motor = BLDCMotor(14);

BLDCDriver6PWM driver = BLDCDriver6PWM(M0_INH_A, M0_INL_A, M0_INH_B, M0_INL_B, M0_INH_C, M0_INL_C, EN_GATE);

// MagneticSensorSPI sensor = MagneticSensorSPI(AS5147_SPI, M0_ENC_A);

// low side current sensing define
// 0.0005 Ohm resistor
// gain of 10x
// current sensing on B and C phases, phase A not connected
LowsideCurrentSense current_sense = LowsideCurrentSense(0.0005f, 10.0f, _NC, M0_IB, M0_IC);

Commander command = Commander(Serial);
void doMotor(char *cmd) { command.motor(&motor, cmd); }

void doA()
{
  encoder.handleA();
}

void doB()
{
  encoder.handleB();
}

void doIndex()
{
  encoder.handleIndex();
}

/*
0 : sensor only
1 : driver only
*/
unsigned char what = -1;

void setup_sensor_only()
{
  encoder.quadrature = Quadrature::ON;
  encoder.pullup = Pullup::USE_INTERN;
  encoder.init();
  encoder.enableInterrupts(doA, doB, doIndex);

  Serial.println("Encoder ready");
}

void setup_driver_only()
{
  bool ret;

  driver.pwm_frequency = 20000;
  driver.voltage_power_supply = 15;
  driver.voltage_limit = 15;
  ret = driver.init();

  driver.enable();
  Serial.println("Driver ready");
}

void setup()
{
  bool ret;

  Serial.begin(115200);

  if (what == 0)
  {
    setup_sensor_only();
    return;
  }

  if (what == 1)
  {
    setup_driver_only();
    return;
  }

  driver.pwm_frequency = 20000;
  // power supply voltage [V]
  driver.voltage_power_supply = 15;
  // Max DC voltage allowed - default voltage_power_supply
  driver.voltage_limit = 12;
  // driver init

  ret = driver.init();
  // link the motor and the driver
  motor.linkDriver(&driver);

  encoder.quadrature = Quadrature::ON;
  encoder.pullup = Pullup::USE_INTERN;
  encoder.init();
  encoder.enableInterrupts(doA, doB, doIndex);

  motor.linkSensor(&encoder);

  // control loop type and torque mode
  motor.torque_controller = TorqueControlType::voltage;
  // resistance : 0.068 ohm
  // motor.voltage_limit = motor.phase_resistance*motor.current_limit
  // here : voltage_limit = 0.068 * current_limit(=.1)
  //
  motor.controller = MotionControlType::velocity_openloop;

  // controller configuration based on the control type
  // velocity PID controller parameters
  // default P=0.5 I = 10 D =0

  motor.PID_velocity.P = .05;
  motor.PID_velocity.I = 1.;
  motor.PID_velocity.D = 0.;

  motor.LPF_velocity.Tf = 0.01;

  // jerk control using voltage voltage ramp
  // default value is 300 volts per sec  ~ 0.3V per millisecond
  motor.PID_velocity.output_ramp = 300;

  // 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;

  // since the phase resistance is provided we set the current limit not voltage
  // default 0.2
  // motor.current_limit = 0.1; // Amps

  // max voltage  allowed for motion control
  motor.voltage_limit = .6;
  // alignment voltage limit
  motor.voltage_sensor_align = 0.5;

  // link the driver
  current_sense.linkDriver(&driver);
  // init the current sense
  current_sense.init();
  current_sense.skip_align = true;
  motor.linkCurrentSense(&current_sense);

  command.add('M', doMotor, (char *)"motor");
  // tell the motor to use the monitoring
  motor.useMonitoring(Serial);
  motor.monitor_downsample = 0; // disable monitor at first - optional

  motor.init();

  motor.initFOC();

  delay(1000);
  next_millis = HAL_GetTick() + 1000;

  motor.move(.5);
}

void loop_sensor_only()
{
  encoder.update();

  float angle = encoder.getAngle();
  float velocity = encoder.getVelocity();
  long pulse = encoder.getPulse();

  // display the angle and the angular velocity to the terminal
  Serial.print("A:");
  Serial.print(angle);
  Serial.print("\t");
  Serial.print("P:");
  Serial.print(pulse);
  Serial.print("\t");
  Serial.println(velocity);
}

void loop_driver_only()
{
  // setting pwm (A: 3V, B: 1V, C: 5V)
  driver.setPwm(3, 1, 5);
}

void loop()
{
  // Read the measured angle
  // float angle = as5047p.readAngle();

  if (what == 0)
  {
    loop_sensor_only();
    return;
  }

  if (what == 1)
  {
    loop_driver_only();
    return;
  }

  // encoder.update();

  // foc loop
  motor.loopFOC();
  // motion control
  motor.move();
  // monitoring
  motor.monitor();
  // real-time commander calls
  command.run();

  /*
    my_angle = encoder.getSensorAngle();

    uint32_t now = HAL_GetTick();
    if (next_millis && (now >= next_millis))
    {
      next_millis = now + 1000;
    }
    */
}

Hey @FredF , welcome to SimpleFOC!

Does your motor actually have 14 pole pairs (I.e. 28 poles) or should it be 7PP?

What’s the motor phase resistance? 0.6V out of 15V may be a little low? Although if it’s enough for open loop then you should also get movement in closed loop.

In closed loop, what do you see on the serial console as the motor initializes? Does the calibration run (motor moves fwd and back a bit) and the pole pair check succeed?

Hi, thank you for your quick response. I will check & grab all those infos and get back to you.

A quick question, however: it’s not clear to me in which unit all the ‘xxx_limit’ settings are expressed? For instance the voltage limit: in what I did, is it really 0.6V, or a fraction of the main input voltage?

Because I doubt that this motor can start with a so low voltage…

Hey,

The limits are indeed expressed in volts, so 0.6V or 600mV is quite low…

The current limit is in Amps.

At startup, the motor search for the zero (ie index) of the sensor, so goes clockwise a bit and then anticlockwise.

Doing so, it states that the number of poles is correct.

Still I cannot understand how 0.6V can start the motor. When I increase it drastically, the motor still runs, but (of course) with a much higher current (and thus becomes very hot).

How can I check that the current measurement works ok ?

During alignment there is another voltage limit used, the motor.voltage_sensor_align

Maybe that’s why it is moving during alignment but not afterwards?

What is the phase resistance of the motor you’re using, which kind of motor is it?

Please test first using torque-voltage mode only. Once this is working well, try velocity and then angle mode. Finally, when all this is working well, add current sensing and try the torque-current modes…
These modes build on each other, so to speak, and its a mistake to try the most complex one first.

Here is my setup together with a photo of the bldc motor. More info tonight :slight_smile:

The bldc motor

Hi,

so I checked, the resistance is pretty low : 0.06 ohms.

I succeed to get something while in velocity loop, but very unstable, by increasing the voltage, as you suggested. But of course, the intensity increases very fast, due to the low resistance.

What I don’t get is that either during calibration (finding the zero of the sensor), or while in velocity_openloop, I get a very smooth movement with a pretty low voltage (like .6 to 1.2 volts). I cannot get anything like this using the velocity loop : I have to increase the voltage to 6 or even 8 volts, and the movement is very irregular (frequently stops, then restarts), not smooth at all, etc.

It’s not clear to me why the velocity loop pid algo cannot deliver more or less what the velocity_openloop delivers, and produce a smooth movement ?

Any other idea ?

Thanks for your help ! :slight_smile:

How about in torque-voltage mode?

What is the behaviour like in this mode? If you set low voltages like 0.1, 0.5V, how does the motor turn?

If it is behaving well in torque-voltage mode then it is a matter of tuning limits, low pass filter and PID settings to get better performance in velocity.

If it is also not working well in torque-voltage mode, then probably it is an issue with the sensor or sensor signal quality…

I will check tonight at home.

Anyway, I appreciate all your feedback and help: thank you so much!

FOC technology is exciting :slight_smile: