Trying to close the velocity loop sensorless

If I do a sensor alignment I get a value of 4.08. Setting motor.zero_electric_angle = 4.08; the motor will not spin CW and spins at terminal velocity CCW.

What is the difference between Commander MSM Sensor | offset: 4.080 and MSE Sensor | el. offset: 0.000

zero_electric_angle should always be 0 for the sensorless observer.

I’ve tried velocity mode with it before and got a bit of wobble in the velocity. I didn’t mess with it for very long, and changed to torque mode with voltage_limit acting as a speed setting (no good for a record player). I normally set I to about 10x P and D=0, so next time I’ll try I=0 and see how it goes.

One possibility would be to use closed-loop angle mode and step the target similar to how open-loop velocity does it. I think observer tracks full rotations, so angle mode will probably work. It will go wonky if you keep spinning long enough due to floating point precision, and there’s currently no solution for it, but I wrote a proposal here a while back https://github.com/simplefoc/Arduino-FOC/issues/302

It will probably work better if you use the experimental angle_nocascade mode to bypass the velocity PID entirely. It requires so little modification, I find it easier to edit my local SimpleFOC source rather than trying to merge it from github. Just add the enum for it in FOCMotor.h, and copy the code in BLDCMotor::move. All it does is replace the velocity PID code with current_sp = P_angle(shaft_angle_sp - shaft_angle); if controller is nocascade. The stuff in BLDCMotor::init is non-essential. You can set P_Angle.limit in your setup function (after calling motor.init, else it will be overwritten) to get the same effect.

The PID values for it are totally different than anything else I’ve encountered. Usually velocity P is around 0.05-0.2 and I around 1-10, and angle P around 10-20 and angle I=0. But in nocascade mode, angle P=100 and I=10000.

Just now I tried playing with angle D as well, and for once it actually helps rather than causing chaos. I have it set to 0.25 (I guess than means I should try values like 0.00025 with regular velocity mode), and it smooths out the motion when doing this angle stepping pseudo-velocity mode, so it should be good for your record player. You can reduce P and I to smooth it more. It results in slower response to disturbances, but that’s not an issue in your case. I’m tuning steppers for a CNC machine, so I want very precise positioning and fast response to step/dir signals.

1 Like

Guys I really appreciate the assistance with getting this going. Thank You..

Now I have proof of concept that FOC can spin the motor at least as stable as the OEM Technics analog PLL with very little tuning I plan to move away from the sensorless observer and use the 3x VRR sensors as virtual Hall sensors.

I used P = 0.045 I found if I used I = 0.1 or more the platter would spin slowly in reverse. Even stopping and trying to spin it forward it would stop and then spin in reverse again.

If I used I it had to be very small like 0.00001 or less.

One question Does I and D reset when I stop the motor??

The only one with persistent state is I, and it should decay to 0 when the motor is stopped (or any other time the velocity remains steadily equal to the target), but it’s not instantaneous. D only remembers one frame worth of history, so it is effectively reset on the next update.

I’ve never seen I cause reverse spinning behavior, but it’s probably too difficult to try and debug exactly what’s going on.

Do the VRR sensors give an analog output, or only on/off? Digital halls aren’t accurate enough for something like a record player, but analog might work. The LinearHall class is designed for two sensors spaced 90 degrees, but can easily be hacked to work with 120 degree spacing.

Another possibility is to simply use open loop. It sounds like your motor is pretty coggy, but you could check with Anthony what kind of steppers he was using here Baffling but extremely convenient phenomena that allows stepper motors to be used in e.g. fans easily

My NEMA23 steppers are pretty noisy, so apparently not just any stepper will work, same as with BLDC motors. Finding a motor that’s naturally smooth is probably a better path to success than trying to improve the control loop. Record players are among the most demanding applications for motor smoothness, due to the extreme sensitivity of human ears.

1 Like

If I set I = 0.1 the motor spins CW then slows and spins CCW.

The 3 VRR (Variable Reluctance Resolver) are fed with 50kHz excitation and then passed through a diode detector. So yes they are analog. My plan was to pass them through a schmitt trigger to simulate Hall sensors, unless there is a better way.

I have seen somewhere the code to calculate Hall offset from 120° to 90°

This motor topology was used as the basis for the Technics SP02 used to drive the later cutting lathes and its cogging is so low the signal (about 33Hz) is in the noise floor of the FM demodulated FG signal.

The motor is a PMSM and has perfect sinusoidal BEMF. The Technic SP10mk2 motor was and still is one of the best turntable drive motors available.

I’m not trying to improve the drive electronics. This is really about CAN I and learning something new.

Oh, well nevermind then. I wonder why the open loop velocity is so wobbly?

It sounds like the I factor is being applied in the wrong direction somehow… Have you tried setting it to a negative value just to see what happens?

Stick this in LinearHall::getSensorAngle after it reads the sensors:
b = a * _1_SQRT3 + b * _2_SQRT3;
It’s one of the Clarke transform components for converting 3-phase to a 2D vector, same as used in CurrentSense::getABCurrents.

Although there is one more problem, that STM32’s implementation of analogRead is too slow to use. It’s also much better to use oversampling to reduce sensor noise. Unfortunately STM32F4’s ADC and DMA are different than G4’s, so my usual ADC setup won’t work directly. I’m not confident I could write a functional configuration without hardware to test on, and you might get more education than you bargained for it you try to finish it yourself :slight_smile:

Would it be difficult to mount a magnetic encoder like MT6835, AS5047P, or AS5048A to it? Don’t use AS5600. It’s popular, but too low resolution.

You can try the schmitt trigger, but I doubt you’ll be able to get it running smoothly enough.

I tried setting I = - 0.1 and the platter takes off backwards at max velocity and can only be stopped by resetting the MCU. M0 does nothing.

Do I put this in the sensorless observer or is this to use if/when I add Hall sensor?

Impossible as the spindle sits on a ball in an oil bath. There is nowhere to attach an additional sensor.

That bit of code goes in the LinearHall class, and will allow it to convert the analog readings from two of the VRR sensors to a high resolution angle. You can give it a try with the default analogRead. I think it takes around 150us per call, so will drop your update rate to around 3KHz. Probably would cause audible stepping of the position, but should give you an idea of whether it’s worth the trouble of writing a proper ADC setup. With the spindle in an oil bath, it may be your only hope :slight_smile:

Or you could try this experimental class I wrote when trying to calibrate my way to perfection with 3 linear hall sensors spaced 120 degrees. It will be even slower for now since it does 3 analogRead calls, but maybe faster with good ADC code since it doesn’t need atan2. And more importantly, may be more accurate.

EDIT: Moved the code I posted here to github, so it’s easier to keep track of and update in the future:

Hopefully the forward and reverse rotation will cancel out the nonlinearity of open loop on your motor. Otherwise it will “burn in” the wobble to the calibration data and accurately reproduce it in closed-loop mode :stuck_out_tongue:

1 Like

I’m adding switches ATM. Once I have this code working then I’ll start on the Hall Sensor.

Question again. With the switch code in the LOOP motor behavior changes and I’m assuming this is because it slows the loop. Is there another way to implement the switches?

// NOTE; Ground Pin PA10 to get DFU and Serial commandT working
/* This code is working 
To get Serial print Ln to display in the Srial Monitor
1 - Ensure Com1 is selected in Tools/Port IDE
2 - Plug USB into STLink and load Code
3 - Ground Pin A10
4 - Plug USBC into PC and in Device Manager select the Com port assigned to the STM32 in this case it was Com5 set bit rate to 115200
5 - Select Com5 in Arduino IDE and reboot STM32 */

// Sensorless FOC for STM32 and PID

#include <SimpleFOC.h>
#include <SimpleFOCDrivers.h>
#include "encoders/MXLEMMING_observer/MXLEMMINGObserverSensor.h"

const int stoppb = PA15;  //Pushbutton Stop switch.
const int RPM33pb = PB3;  //Pushbutton 33 RPM Start switch.
const int RPM45pb = PB4;  //Pushbutton 45 RPM Start switch.
const int stopLED = PB6; // Stop LED indicator
const int LED33 = PB7;   // 33 RPM LED indicator
const int LED45 = PB8;   // 45 RPM LED indicator

int stopState = 0;       // Stop
int RPM33State = 0;      // 33 RPM start
int RPM45State = 0;      // 45 RPM start
int RPM33 = 3.4;         // 33 RPM
int RPM45 = 4.38;         // 45 RPM

BLDCMotor motor = BLDCMotor(10, 14, 27, 0.1); 

BLDCDriver3PWM driver = BLDCDriver3PWM(PA7, PA6, PA5, PA3);  // STM32


// MXLEMMING observer sensor instance
MXLEMMINGObserverSensor observer = MXLEMMINGObserverSensor(motor);

// Lowside current sensor instance
LowsideCurrentSense current_sense = LowsideCurrentSense(0.01f, 50.0f, PA1, PA2); // With these settings M0 commands now stop motor


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

void setup() {

 {
  pinMode(stopLED, OUTPUT);       //Wire to stop button LED
  pinMode(LED33, OUTPUT);         //Wire to 33 button LED
  pinMode(LED45, OUTPUT);         //Wire to 45 button LED
  pinMode(stoppb, INPUT_PULLUP);
  pinMode(RPM33pb, INPUT_PULLUP);
  pinMode(RPM45pb, INPUT_PULLUP);

}
  _delay(1000);

  // use monitoring with serial
  Serial.begin(115200);

   // link the motor to the sensor
  motor.linkSensor(&observer);

  // driver config
  // power supply voltage [V]
  driver.voltage_power_supply = 24;
  motor.voltage_limit = 9.5;
  driver.init();
  // link driver
  motor.linkDriver(&driver);
  // link current sense and the driver
  current_sense.linkDriver(&driver);

  // current = voltage / resistance, so try to be well under 1Amp
  motor.current_limit = 1.0;  // [Amps]

  // set control loop type to be used
  motor.controller = MotionControlType::velocity;
  motor.torque_controller = TorqueControlType::voltage;

  // velocity PID controller
  motor.PID_velocity.P = 0.065;       //0.065  
  motor.PID_velocity.I = 0.00005;     //0.00005
  motor.PID_velocity.D = 0.000001;    //0.000001

  // 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.05;  // 0.05

  // current sense init and linking
  current_sense.init();
  motor.linkCurrentSense(&current_sense);

  // initialise motor
  motor.init();
  // skip the sensor alignment
  motor.sensor_direction = (Direction::CW);
  motor.zero_electric_angle = 0.0;
  motor.initFOC();
 
  // Run user commands to configure and the motor (find the full command list in docs.simplefoc.com)
  Serial.println("Motor ready.");

  _delay(1000);
}

void loop() {
  // iterative setting FOC phase voltage
  motor.loopFOC();
  
stopState = digitalRead(stoppb);  // Read stop push button
RPM33State = digitalRead(RPM33pb);   // Read 33 push button
RPM45State = digitalRead(RPM45pb);   // Read 45 push button

if (stopState == LOW)    // Stop platter
{ motor.move(0.0);}

 if (RPM33State == LOW) // Run 33RPM until Stop switch pressed.
  {motor.move(RPM33);}

if (RPM45State == LOW) // Run 45RPM until Stop switch pressed.
  {motor.move(RPM45);}

}

No, that looks fine. Should be almost no added loop time. The problem is that it doesn’t call move at all if none of the buttons are pressed. I would suggest something like this:

 if (stopState == LOW)    // Stop platter
  { motor.target = 0;}

 else if (RPM33State == LOW) // Run 33RPM until Stop switch pressed.
  {motor.target = RPM33;}

 else if (RPM45State == LOW) // Run 45RPM until Stop switch pressed.
  {motor.target = RPM45;}

motor.move();

motor.move can take a target value as an argument for convenience, but this way works too.

And it’s generally better practice to use “else if” for mutually exclusive actions, so only one of them will execute. With your original code, motor.move would be called multiple times if multiple buttons were pressed at once. It doesn’t actually matter anymore since only target would get set multiple times in one frame, but still a good habit.

1 Like

Thanks that fixed the problem. It was still running slower than without the switch code in the loop but I was able to adjust this and it’s now as stable as previous.

Next I’ll look into different sensors. I have been toying with the idea of adding an incremental encoder to the outside of the motor rotor. The encoder wheel would need to custom made as the rotor is 140mm OD.

How do I change the PID sample rate. I’ve searched but can’t find any reference to simpleFOC PID sample rate…

It updates however often you call it, divided by (motor.motion_downsample+1). But it adapts to the time between calls so the behavior is mostly the same regardless.

How do I call it? I’ve looked at other Arduino PID code but those commands didn’t work.

I only have this code in the sketch with no line on how often it’s called.

  // velocity PID controller
  motor.PID_velocity.P = 0.065;       //0.065  
  motor.PID_velocity.I = 0.00005;     //0.00005
  motor.PID_velocity.D = 0.000001;    //0.000001

  // 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.05;  // 0.05

Oh, sorry. motor.move is where the PID update(s) are called, depending on the selected control mode (motor.controller). And the controllers are all cascaded. Torque mode is the bottom. Velocity mode updates velocity PID, which outputs target torque. Angle mode updates angle PID (named motor.P_angle because most of the time I and D should be 0), which outputs target velocity.

Torque is a bit confusing because there are 3 versions of it:

  1. The simplest one just sets voltage.q = target, properly called voltage control (similar to a DC motor, it will lose torque as speed increases and slow down/heat up under load).
  2. If motor.phase_resistance and KV_rating are set, it will calculate the back EMF from the measured velocity and add it to voltage.q, giving approximate current control (torque is proportional to current). It isn’t accurate when the velocity is changing quickly, so with low resistance motors you can burn mosfets easily.
  3. With current sense, it just sets current_sp = target. Then loopFOC uses two PID controllers to regulate current.q toward current_sp and current.d toward 0. The PID controllers output voltage.q and voltage.d, and in most cases they don’t need manual tuning.

All 3 versions apply voltage.q and voltage.d to the motor in loopFOC where it calls setPhaseVoltage.

1 Like

Can I change how often motor.move is called or does it just execute once every loop?

EDIT:

I guess I should detail the problem I’m trying to solve..

When I reset the MCU speeds at 33.33 and 45rpm are correct. If I change speed 33 to 45 and back 33.33rpm is 10% fast and does not slow with stop function. I have to reset the MCU to get speed correct again. I’m assuming this is a problem with the Integral value..

EDIT again..

I think if I reset I between speed changes it might solve the issue. I found this code which I’ve spliced into my FOC code but I get an error and have no idea

“Compilation error: ‘BLDCMotorController’ does not name a type”

#include <SimpleFOC.h>

// Create a BLDC motor instance
BLDCMotor motor = BLDCMotor(11); // Replace 11 with your motor's pole pairs

// Create a controller instance
BLDCMotorController controller = BLDCMotorController();

// Function to reset the integral term
void resetIntegral() {
    controller.integral = 0; // Reset the integral term
}

void setup() {
    // Initialize motor and controller
    motor.init();
    controller.init();
}

void loop() {
    // Your control logic here

    // Call resetIntegral() when needed
    if (/* condition to reset integral */) {
        resetIntegral();
    }
}

I found the solution after a lot of hours searching.

I found this and added it in void loop motor.PID_velocity.reset();

EDIT: Can someone put this in the documentation