Stepper Motor Closed Loop issue

Hey everyone,

Hi, I’m starting a project to design a board based on STM32 and DRV8412 to control a stepper motor using SimpleFOC. Before sending the boards to produce I am doing a firmware validation to be sure that I can get it to work.

The test setup is made up of a Nema 17 Stepper Motor (Hanpose 17HS4401) a L298N driver and an STM32 as µController.

Captura de Pantalla 2023-04-16 a las 1.01.49


1.- My motor has step angle: 1.8 degrees. I assumed 50 pole pairs . How can I know how many magnetic poles my steper motor has? In Open loop it works quite correctly although it doesn’t make a complete turn every 3 Pi radians. Could it be due to an incorrect configuration of the magnetic poles?


2.- In closed loop it does not work. The behavior is completely erratic. I have verified that the magnetic position sensor works correctly. Is there something wrong with my caigo that could cause the problem?



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

///    SPI 2 (AS5048A)
#define MOSI_ENC  PA7   // Blue
#define MISO_ENC  PA6   // Green
#define CLK_ENC   PA5   // Orange
#define CS_ENC    PA4   // Yellow

#define PWM_A PC7   // TIM8_CH2   PWM_A -> IN1
#define PWM_B PC6   // TIM8-CH1   PWM_C -> IN2
#define PWM_C PA1   // TIM2_CH2   PWM_B -> IN3
#define PWM_D PA0   // TIM2-CH1   PWM_D -> IN4

#define RESET_AB PB14
#define RESET_CD PB13

#define LOOP_PIN PC13

// Stepper motor & driver instance
StepperMotor motor = StepperMotor(50);
StepperDriver4PWM driver = StepperDriver4PWM(PWM_A, PWM_B, PWM_C, PWM_D, RESET_AB, RESET_CD);

MagneticSensorSPIConfig_s AS5047A_SPI_Config{
    .spi_mode = SPI_MODE1,
    .clock_speed = 100000,
    .bit_resolution = 14,
    .angle_register = 0x3FFF,
    .data_start_bit = 13,
    .command_rw_bit = 14,
    .command_parity_bit = 15};

MagneticSensorSPI sensor = MagneticSensorSPI(AS5047A_SPI_Config, CS_ENC);
SPIClass spiConnectionEncoder(MOSI_ENC, MISO_ENC, CLK_ENC);

Commander commander = Commander(Serial);


void doMotor(char *cmd){
  commander.motor(&motor, cmd);
}

void configureMotor(){

  // control loop type and torque mode
  motor.torque_controller = TorqueControlType::voltage;
  motor.controller = MotionControlType::velocity_openloop;
  motor.motion_downsample = 0.0;
  driver.voltage_power_supply = 12;
  
  // velocity loop PID
  motor.PID_velocity.P = 10.0;
  motor.PID_velocity.I = 10.0;
  motor.PID_velocity.D = 0.0;
  motor.PID_velocity.output_ramp = 1000.0;
  motor.PID_velocity.limit = 20.0;
  // Low pass filtering time constant
  motor.LPF_velocity.Tf = 0.2;
  
  // angle loop PID
  motor.P_angle.P = 10.0;
  motor.P_angle.I = 1.0;
  motor.P_angle.D = 0.0;
  motor.P_angle.output_ramp = 0.0;
  motor.P_angle.limit = 10.0;
  // Low pass filtering time constant
  motor.LPF_angle.Tf = 0.3;
  
  // current q loop PID
  motor.PID_current_q.P = 3.0;
  motor.PID_current_q.I = 300.0;
  motor.PID_current_q.D = 0.0;
  motor.PID_current_q.output_ramp = 0.0;
  motor.PID_current_q.limit = 12.0;
  // Low pass filtering time constant
  motor.LPF_current_q.Tf = 0.005;
  
  // current d loop PID
  motor.PID_current_d.P = 3.0;
  motor.PID_current_d.I = 300.0;
  motor.PID_current_d.D = 0.0;
  motor.PID_current_d.output_ramp = 0.0;
  motor.PID_current_d.limit = 12.0;
  // Low pass filtering time constant
  motor.LPF_current_d.Tf = 0.005;

  // Limits
  motor.velocity_limit = 30.0;
  motor.voltage_limit = 12;
  // motor.current_limit = 2.0;

  //  general settings
  //  pwm modulation settings
  motor.foc_modulation = FOCModulationType::SinePWM;
  motor.modulation_centered = 1.0;
}

void setup(){

  bootLedIndicator();
  Serial.begin(115200);
  Serial.println("Starting Motor configuration ....");
  
  //SimpleFOCDebug::enable(&Serial);

  char motor_id = 'M';
  commander.add(motor_id, doMotor, "Motor");

  pinMode(LOOP_PIN, OUTPUT);

  motor.useMonitoring(Serial);

  sensor.init(&spiConnectionEncoder);
  // link the motor to the sensor
  motor.linkSensor(&sensor);
  configureMotor();
  driver.init();
  // link the motor and the driver
  motor.linkDriver(&driver);
  motor.target = 10;
  // initialize motor
  motor.init();
  motor.initFOC();
  Serial.println("Motor ready.");
  _delay(1000);
}


void loop(){
  motor.loopFOC();
  motor.move();
  commander.run();
  motor.monitor();
  digitalToggle(LOOP_PIN);
}


Thank you very much in advance for the suggestions

You can see the formulas for relationship between number of teeth on the rotor (poles) , number of phases, and the resolution here: Stepper Motor Basics: PM vs VR vs Hybrid

For regular stepper motors this is the “permanent magnet” type. With parameters 1.8 degree and 2 phases, you get 50 poles. So maybe you should try with 25 pairs? I think 50 teeth is the regular stepper motor configuration unless you have special requirements.

For open loop, the pole pair count is not so important, but this can cause lots of problems with closed loop. It might just be an issue with this.

1 Like

Hi @VIPQualityPost

Thank you for your answer :slight_smile: My motor is nothing especial. As you can see in the following picture it has 50 teeth.

  • 25 magnetic poles: Not move at all in any control mode
  • 100 magnetic poles: It moves using open loop velocity mode but not move al all using using open loop angle mode.
  • 50 magnetic poles: It moves both with open loop velocity and able mode but the behavior seems quite messy since request to go to 2Pi position instead af performing a full rotation it performs more or less a half rotation. Why asking to come back to 0 position wt always return to the same position where departed,

I have tested the magnetic position sensor in stand alone and it works perfectly.

Does anyone know what could be wrong with my setup?


I think taking the stepper motor apart causes problems, because the rotor is magnetized after it is installed, and the eddy current formed when removing it might demagnetize it. Do you have a motor which was not opened to try?

The motor presents the same as before being opened. Fortunately, it hasn’t gotten worse… nor has it improved.

Yes, I have a similar motor that I have never noticed but the behavior is exactly the same.

I guess when I’m in the open loop position I command the motor to go to the 2Pi position, it should make one full turn… which it doesn’t. Do you know what could be the cause of it?

Here is some very old code (Oct 2020 - SimpleFoc v1.6) that I made to go along with this video with closed loop nema 17. Perhaps it’ll give some clues:

#include <SimpleFOC.h>
MagneticSensorSPI sensor = MagneticSensorSPI(AS5147_SPI, PA15);

StepperMotor motor = StepperMotor(PB1, PB0, PA6, PA7, 50);

// these are valid pins (mosi, miso, sclk) for 2nd SPI bus on storm32 board (stm32f107rc)
SPIClass SPI_2(PB15, PB14, PB13);

float target = 8;

// utility function enabling serial communication with the user to set the target values
// this function can be implemented in serialEvent function as well
void serialReceiveUserCommand() {
  
  // a string to hold incoming data
  static String received_chars;
  
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the string buffer:
    received_chars += inChar;
    // end of user input
    if (inChar == '\n') {
      
      // change the motor target
      target = received_chars.toFloat();
      Serial.print("Target velocity: ");
      Serial.println(target);
      
      // reset the command buffer 
      received_chars = "";
    }
  }
}

void setup() {

  
  // monitoring port
  Serial.begin(1000000);
  delay(750);
  // SPI_2.setClockDivider(4);
sensor.init(&SPI_2);
   motor.LPF_velocity.Tf = 0.1;

  motor.linkSensor(&sensor);
  motor.voltage_sensor_align = 2;
  motor.voltage_limit = 5;
  
  motor.PID_velocity.P = 3;
  motor.PID_velocity.I = 2;
  motor.PID_velocity.D = 0.0;//01;
  // motor.PID_velocity.D = 1000;

  motor.P_angle.P = 2;
  motor.P_angle.I = 0;
  motor.P_angle.D = 0.0;//01;

  motor.controller = ControlType::angle;
  motor.useMonitoring(Serial);
  motor.init();
  motor.initFOC();
  

  Serial.println("Sensor ready");
  sensor.getVelocity();
  // _delay(1000);
}

int sensorHz = 800;
long sensor_interval_micros = 1000000 / 200;

void loop() {
  // display the angle and the angular velocity to the terminal
  // delay(200);
  // sensor.getAngle();
  
  motor.loopFOC();

  static long last = micros();
  long now = micros();
  if (now - last > sensor_interval_micros) {
    motor.move(target);
    // Serial.println(sensor.absolute_angle);
    last = now;
  }


Serial.print(" "); Serial.print(" "); Serial.print(sensor.getVelocity2()); Serial.println();
  serialReceiveUserCommand();
}

I seem to remember sensor alignment was hard - I may have tweaked the alignment value after auto alignment. If you notice that motor behaves better in one direction (clockwise vs anticlockwise) it is probably sensor alignment being slightly off.

Also I can see I did something equivalent to motor.motion_downsample to reduce the amount motor.move is called.

2 Likes

Thank you very much @Owen_Williams :slight_smile: I will test your code to see if makes any difference.

What gets me crazy is tahr in open loop, without magnetic sensor when I command to turn to the 2Pi position, instead of making a full rotation it just performs about a half rotation. I can not understand what can be the reason to behave in tat way :confused: Any idea about what could be the cause ?

Regards and nice to see you back here :slight_smile:

Two explanations come to mind:

  1. the sensor is “doubled” - this happens for example when the bit-positions are “off by one” and you’re reading the angle values shifted to the left by 1 position. Then the sensor will return 2 rotations for each real rotation, if you see what I mean.
  2. in open-loop mode, the pole-pairs are wrong (by 2x) - so if you set 50PP, then the real value is 100PP… in open loop mode, setting the PP wrong won’t affect the function, except the real angle values will be scaled by the PP error. So this would also explain why you get 1/2 rotation instead of a full rotation.
1 Like

HI @runger ,

I seem to have made some progress, at least now it seems to work perfectly fine using open loop modes, but it still has strange behavior when switched to closed loop. To make it work in openloop mode, the maximum speed was limited to 5. It seems that when the limit was 20 it was as if it was “losing steps”. I’m not sure why it can’t work with a speed limit of 20.

t seems to work but suddenly loses control and later regains control again. These cycles of control and loss of control are repeated.

When I test the magnetic sensor in standalone mode itself it works correctly offering consistent readings without noise.

Any idea what could be the origin of the sudden loss of control that you the motor suffers?

You can see in this video that its seems to keep a velocity, the it get crazy and suddenly recovers the control until loses the control again ,

EDIT1: I have also observed that if I command a low velocity it permiso much better . I continues loosing control but it happens less frequently and for shorter periods os time

Hi @Owen_Williams

It seem to me that you are using a Storm32 controller to drive the stepper motor. How do you connect the stepper motor that requires 4 pins to the Storm32 the has 3 output pins ?

I briefly show in the video 26 seconds in. The storm32 can drive 3 gimbals so has 9 PWM outputs. I use 4 of those 9 to drive the stepper. It worked, I probably got lucky, i.e the pins I chose were on the same timer.

The storm32s are fun to play with - e.g I also rewired part of the board to free up an SPI (i think from an LED perhaps?).

After this experiment I concluded that nema 17s were hard to work with - hard to align and that I couldn’t get them going very fast. I went back to using gimbals.

1 Like

Ok, I understand where the trick is!!! I assumed that Storm32 was the typical controller capable of controlling a single BLDC motor but, in fact, it is capable of controlling 3 BLDCs. Since you have 9 half-bridges, you can use 4 of them to drive a single stepper motor, or 8 to drive a pair of steppers :slight_smile:

I agree that if I don’t run it at low speed the motor “loses steps” even when I run it in open loop mode.

What I don’t know is if it is due to L298N limitations, if the same happens with other drivers or the source of this behavior is due to the NEMA17 and can be avoid using a better stepper stepper motors.

Could you time just the sensor doing 10.000 reads / iterations ? When I was testing the MT6835 I saw a lot of improvement when sampling without SPI.begintransaction(). Dont know the AS5048A implementation.

AS5048A works perfectly fine with BLDC motors my guess is that is not related with a SimpleFOC implementation.

This takes 1130 µS

void loop() {
    sensor.update();
    Serial.println(sensor.getAngle());
    digitalToggle(LOOP_PIN);
}

This takes 270 µS

void loop(){
  motor.loopFOC();
  motor.move();
  commander.run();
  motor.monitor();
  digitalToggle(LOOP_PIN);
}

Not sure why reading the sensor in standalone mode is more time consuming that performing a regular execution

uint16_t AS5048A::spi_transfer16(uint16_t outdata) {
	if (nCS>=0)
		digitalWrite(nCS, 0);
	spi->beginTransaction(settings);
	uint16_t result = spi->transfer16(outdata);
	spi->endTransaction();
	if (nCS>=0)
		digitalWrite(nCS, 1);
	// TODO check parity
	errorflag = ((result&AS5048A_ERRFLG)>0);
	return result;
}

Did you include the Serial.println() into the time measured? That may take very long.

uint16_t MT6835_STM32G4::getRaw16bitTIM3(){

return TIM3->TNT;

}

Yes you are right :slight_smile: . Without Serial.println it takes just 260 µS. I continue to struggle figuring out what can be the case of the erratic behavior when running in closed loop mode :confused:

I had an issue with a different encoder (MT6071 on I2C)where the bitmask used for returning the angle between the upper and lower register data was wrong.
Could you try to just plot output values from the sensor, and turn it _2PI degrees, and read the angle?
If you are thinking it’s an issue with local field made by the motor, you can also just set the phase voltage for one or both phases, and then backdrive by hand for a similar exercise but with static external B field.

Hi @VIPQualityPost

Thank you for your suggestions :slight_smile:

When I test the magnetic sensor in standalone configuration everything seems to works perfectly , I have also used this same magnetic sensor with BLDCs motors in closed loop configuration and worked perfectly fine. My guess is that is not a firmware problem. Maybe I have to try with a different motor or a different magnetic sensor to try to overcome the issue.