Stepper motor unintentionally changes direction

Hey Guys,
I have a problem with my Motor. It is slowly getting faster, then stops and it changes its direction even though I set a consant voltage. When I try to stop it, it also changes direction.
Does anyone know what causes this? maybe an issue with the encoder, that it is somehow loosing pulses? Although it is mounted securely to the shaft.
I am using a stm32 bluepill, Nema 34 stepper motor, 2000ppr encoder and a custom motor driver, which basically consists of 2 H Bridges (StepperDriver4PWM). I can also share the scematics if someone wants me to.
Thanks in advance for your help.

Here is a video showing the problem: motor test - YouTube

Here is my code:

#include <Arduino.h>
#include "SimpleFOC.h"

// Stepper motor instance
StepperMotor motor = StepperMotor(50);

// Stepper driver instance
StepperDriver4PWM driver = StepperDriver4PWM(PB6, PB7, PB8, PB9,  PA1, PA2);

// encoder instance
Encoder encoder = Encoder(PB13, PB14, 2000);

// interrupt routine intialisation
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}

void setup() {
  //pinMode(PC13, OUTPUT);
  //digitalWrite(PC13, HIGH);
  Serial.begin(9600);
  Serial.println("TEST!loluhk");
  motor.useMonitoring(Serial);

  motor.monitor_variables = 0b0100000;       //0b0100000 | 0b0010000 | 0b0000010 | 0b0000001 ; // q value | d value | velocity value | angle value

  driver.pwm_frequency = 25000;// pwm frequency to be used [Hz]

  //Encoder
  encoder.quadrature = Quadrature::ON;// enable/disable quadrature mode
  encoder.pullup = Pullup::EXTERN;// check if you need internal pullups
  encoder.init();// initialise encoder hardware
  encoder.enableInterrupts(doA, doB);// hardware interrupt enable
  motor.linkSensor(&encoder);

  motor.foc_modulation = FOCModulationType::SinePWM;// choose FOC modulation
  driver.voltage_power_supply = 24;// power supply voltage [V]
  driver.voltage_limit = 24;// Max DC voltage allowed - default voltage_power_supply
  driver.init();
  motor.linkDriver(&driver);// link the motor to the sensor
  motor.voltage_sensor_align = 6;// aligning voltage [V](default 6V)
  motor.torque_controller = TorqueControlType::voltage;
  motor.controller = MotionControlType::torque;  // set control loop type to be used

  motor.init();// initialise motor
  motor.initFOC();// align encoder and start FOC
  motor.target = 6;// set the initial target value

  Serial.println("Setup Complete!lol");
  _delay(1000);
}
void loop() {
  motor.loopFOC();// iterative setting FOC phase voltage
  // iterative function setting the outter loop target
  // velocity, position or voltage
  // if tatget not set in parameter uses motor.target variable
  motor.move(5);
  motor.monitor();
}

Hey @Niob,

The video shows what happens when you execute exactly this code?
Could you remove the monitoring to see if the performance is better?

When re watching the video I think its an issue with the sensor. Our initial encoder implementation uses the interrupt based encoder handling. But bluepill has the hardware encoder interface so you might try using it.
Did you maybe forget to add some pull-ups?
If you do the velocity control and set lower velocities (6 rad/s) does the motor change direction?

You can look into the SimpleFOC drivers library which has it implemented:

Its easy to integrate, you just need to make sure that you are using the good pins of your bluepill, which support the hardware encoder interface.

Here is the picture from the STM32CubeMX telling you which pins you could use:
image

Pins PB6, PB7, PB8, PB9 belong to the timer 4, which means that you can use tim1, tim2 or tim3 with you encoder:
So either:

  • PA0 and PA1
  • PA9 and PA8
  • PA6 and PA7

Hi @Niob , hi @Antun_Skuric , there is something going on here…

On Discord a similar problem was described by @Pipe

A video of the problem…

It’s also a stepper.

I have the feeling the high pole count of the stepper interacts with the sensor in some way that at some speed it misses a “full turn increment” and then the motion control sends it off in the other direction… something like that, but I haven’t fully figured it out yet.

You are right that there are quiet a few issues like this one so far. But in my opinion the mian issue was the EM field generated by the motor and does some strange things with the magnetic sensors. It was pretty specific to the magnetic sensors though. This one is new :smiley:

Ah yeah?
This should be an issue only for the angle and velocity mode?

I’m still thinking about it. I don’t really use the steppers, so I don’t have a test setup for that.
But it seems really strange to me:

  • when pipe turns the motor fast one way, the error occurs, and it starts spinning the other direction
  • when he turns the motor fast the other way, the error does not occur
  • when he reverses the direction of the motor, the problem occurs in the same way, but in the reverse sense - so it depends on the direction the motor was commanded to run in
  • it’s happening in voltage mode, not velocity

Hi,
Thanks a lot for your replies @Antun_Skuric and @runger.
I’ve implemented the Encoder mode of the STM32, so now I am processing the encoder signals in hardware.
I have also tested the encoder and it counts up to exactly 8000 (in quadrature mode) each revolution. I have used the index pin to indicate one exact full revolution.
Because it is not loosing pulses no matter how fast I move it, I think the alignment procedure is too unprecise for the high pole count of the Stepper. I’ll have a look at that.
I don’t think EMI is an issue with my setup. I have my motor grounded and the encoder signals are also clean,partly because i run them through a Schmitt-Trigger first.
Also I’m using 1kOhm pull-ups and the issue is still there, even with the monitoring removed.

Hey Niob,
Very nice overview!
What you could try is remove the sine function approximation:

Basically remove the _ before sin and cos:

float _ca = cos(angle_el);
float _sa = sin(angle_el);

This would remove issues due to the eventual sine wave approximation.

this is something I was thinking about… would you say @Antun_Skuric that the size of the sine table has a relation to the PWM resolution?
And would it make sense to make the sine table dynamically sizeable? computing it during setup seems like an acceptable performance hit, and might allow people to determine their personal accuracy/memory tradeoff?

Removing the sine wave approximation maybe helped a little bit, but didn’t eliminate the issue. I still think it’s because of the alignment, because sometimes the motor speeds up quickly and then i restart the MCU and it takes longer for it to speed up.

I have also tried the following to eliminate issues due to the encoder not being precise enough. But the issue still persists.

    float tzero_electric_angle1 = 0.0;
    float tzero_electric_angle2 = 0.0;
    float tzero_electric_angle3 = 0.0;
    float tzero_electric_angle4 = 0.0;
    // align the electrical phases of the motor and sensor
    // set angle -90(270 = 3PI/2) degrees

    //Alignment 1 
    setPhaseVoltage(voltage_sensor_align, 0,  _3PI_2); 
    _delay(700);
    encoder.update();
    tzero_electric_angle1 = _normalizeAngle(_electricalAngle(sensor_direction*sensor->getAngle(), pole_pairs));
    _delay(5);

    //move 1 electrical revolution
    for (int i = 0; i <=500; i++ ) {
      float angle = _3PI_2 + _2PI * i / 500.0;
      setPhaseVoltage(voltage_sensor_align, 0,  angle);
      _delay(2);
    }
    _delay(700);

    //Alignment 2
    setPhaseVoltage(voltage_sensor_align, 0,  _3PI_2);
    _delay(700);
    encoder.update();
    tzero_electric_angle2 = _normalizeAngle(_electricalAngle(sensor_direction*sensor->getAngle(), pole_pairs));
    _delay(5);

    //move 1 electrical revolution
    for (int i = 0; i <=500; i++ ) {
      float angle = _3PI_2 + _2PI * i / 500.0;
      setPhaseVoltage(voltage_sensor_align, 0,  angle);
      _delay(2);
    }
    _delay(700);

    //Alignment 3
    setPhaseVoltage(voltage_sensor_align, 0,  _3PI_2);
    _delay(700);
    encoder.update();
    tzero_electric_angle3 = _normalizeAngle(_electricalAngle(sensor_direction*sensor->getAngle(), pole_pairs));
    _delay(5);

    //move 1 electrical revolution
    for (int i = 0; i <=500; i++ ) {
      float angle = _3PI_2 + _2PI * i / 500.0;
      setPhaseVoltage(voltage_sensor_align, 0,  angle);
      _delay(2);
    }
    _delay(700);

    //Alignment 4
    setPhaseVoltage(voltage_sensor_align, 0,  _3PI_2);
    _delay(700);
    encoder.update();
    tzero_electric_angle4 = _normalizeAngle(_electricalAngle(sensor_direction*sensor->getAngle(), pole_pairs));
    _delay(5);

    //calculate average
    zero_electric_angle = (tzero_electric_angle1 + tzero_electric_angle2 + tzero_electric_angle3 + tzero_electric_angle4) / 4;
    _delay(20);

Hey @Niob,

What is the nature of the problem that you’re having. Is the bahavior still the same?
The motor runs away after you apply a force to it?
Or this is restored but the issue is the uneven acceleration in two directions?

Even though I give the motor a constant target, it gets faster.
After reaching a certain speed it gets slower again and stops.
Then it changes direction, gets faster again and the whole thing repeats.
I would expect it to spin in the same direction.

Lets say the motor is turning clockwise.
If I grab it and turn it counterclockwise slowly, I can feel some torque counteracting my movement. If I turn it counterclockwise faster, there is almost 0 torque counteracting my turning.

That is normal actually. When you push in the direction the motor is spinning you will be helping it ( or it will be helping you to push) and you should not feel any resistance.

For me it seems like a problem of a sensor that misses counts making the electrical offset grow and changing the motor directiom. What is the voltage target you are setting?
Have tried with a small value?

Yeah that was what i was initially thinking too. It makes sense.
If that’s the only thing that could cause this behaviour I’ll look at the encoder again tomorrow.

If there is a small error during alignment, the electrical offset will not grow, right?
So the worst thing that could happen if the alignment isn’t correct, is that the offset isn’t exactly 90°, thus the motor is not generating maximum torque.

My Power supply voltage is 24V. For most of my tests I have set the voltage target to 5V.

From my experience this is very similar to the behavior of increasing error. But I agree that there is something strange here and if you’re using the stm32 hardware encoder it is very unlikely that you’re loosing steps. Unless it’s a hardware issue somehow.

Exactly the error will not grow. It can only produce some inefficiency. But never grow if it grows it has to be that the issue is sensing (hardware or software).

If you set 2V does it do the same thing?

Also did you try to close the loop in velocity mode?
Set the target velocity at some low value <10 rad/s cca 1-1.5 rotation per second. And see if your motor can maintain it. And try the same trick as with the voltage mode - pushing the motor, fighting it.
If the velocity is low ( you can even set the target velocity to 0), and if you use the closed loop velocity mode, when you push the motor in any direction your motor will fight you.
If in that case, when you push the motor hard, the motor runs away, that probably means that it’s due to the EMI. The current that builds up, causes some position
sensing issues. Stepper are really bad in terms of EEMI.

You will be able to test this also if you are in the velocity mode, and if the motor runs away when you push it due to the EMI, this behaviour will stop if you lower sufficiently the voltage limit, less voltage less current, less EMI

Thanks for your reply.
I tested it with a 4V aligning voltage and a voltage target of 2V and the issue is gone.
So it most likely is because of EMI, right?
I will try to fix the issue by using the index of the encoder. I’ll set up an interrupt for the index and if it is triggered, I’ll set the Encoder counter to zero. So if the value drifts off, it will be corrected every revolution.
Besides that, do you have any tips on mitigating EMI?
I’ve already seperated the Power and logic part of the circuit. I am using a shielded Encoder cable and the shielding is connected to the circuit ground. Also the motor housing is connected to earth ground.
Should I also use a shielded motor cable and would it help if I connect the circuit ground to earth ground as well?

Just as an aside, depending on the exact STM32 MCU type, the timers have native support for this - encoder mode + index…

Hey @runger,
Thanks for the information.
Do you mean the encoder mode also has an option to reset the counter in hardware?
If so, do you have any information on that?
Because in the reference manual RM0008, they just say: “The third
encoder output which indicate the mechanical zero position, may be connected to an
external interrupt input and trigger a counter reset.”
So I think it isn’t possible with the Bluepill.
The Slave Mode Reset Mode also doesn’t work in combination with the Encoder mode.
I’ll try doing it with an interrupt.

Yes, exactly.

Agreed. The encoder support is a bit different on the different MCUs. The Index handling might be a STM32G4 feature, not sure.

Ok so doing it with an interrupt works fine.
I’m still having problems with the alignment though.
Sometimes the motor turns relatively fast and when I restart the microcontroller it turns relatively slow, indicating that the encoder isn’t perfectly aligned.
This is due to the high pole count of the stepper and the rather low resolution of only 2000ppr.
I’ll try implementing a more precise alignment method.
Do you have any Ideas on that?
The motor should reach it’s maximum speed at a set voltage with no load attached, if the encoder is perfectly aligned, right?
So maybe keep changing the zero_electric_angle until the max speed is reached?
Or doing multiple alignments, each one electrical revolution apart and calculating the average?

Btw, Is there any way to donate something to you guys or the project?
Because I really like the project and you guys have been very friendly and helpful.

I’m in the same boat. Even with a magnetic encoder with 2^14 resolution it seems the zero_electric_angle estimate is too noisy for stepper use - only one sample is taken.

Currently I just found the best zero_electric_angle by hand.

I will look into using more samples and compensating for systematic error in magnetic encoders doing one revolution.