Stepper motor + AS504B I2C closed loop issues

I’m using a L298N driver with a standard NEMA23 stepper on Arduino. I verified my wiring and other settings for the motor using the open loop angle control sketch, and sending 6.28 does get me exactly 1 rotation. When I try any of the closed loop examples with this sensor though, it just jumps a little bit or violently shakes. Even playing with PID values I’m not able to make the motor move at all.

I also have the angle sensor on a gear on a 4.5:1 ratio with the motor (for every 1 rotation of the sensor, the motor will rotate 4.5 times.) I’ve tried editing the read() function inside the MagneticSensorI2C.cpp file so that the value is multiplied when read (and modulo 16383) but this doesn’t work. I have also tried mounting the sensor on the motor directly to no avail. If possible, I’d rather keep the sensor on the gear, since my project is using 2 of them at a specific offset and preferably I’d like to not add a 3rd. if this isn’t possible at all, I’m fine with that too.

Any help is greatly appreciated! Thanks

Hey @wocsor,

At the moment we do not actually support the hearing of the sensor. But you should be able to impleme it quier easily. Do not modify the read function. You can modify the getAngle function in the StepperMotor. Just multiply the angle by 4.5. this will modify your angle loop also ( you will need to set the target values divided by 4.5). :slight_smile:

Now, i am really not sure why it does not work.

Did you verify that your sensor works well?
Did you try voltage control mode ?

Thanks for the quick reply! I’ll try implementing that inside the getAngle function. I did verify the angle sensor with other code, but it also seems to report correctly if I call getAngle and convert from rads. I wasn’t sure whether voltage control mode was supposed to hold the motor steady or spin it, but that example seems to just lock the motor in place and cause the driver to heat up.

If things are getting hot, lower the voltage_limit.

What do you have your pole_pairs set to? From recollection it should be 50.

Thinking out loud, i wonder if you could not change angle code and instead set pole pairs to 50 x 4.5 = 225? Unless I’ve got the ratio back to front this is effectively the number of pole pairs you’ll be going through per complete gear/sensor revolution.

225 is a relatively high number and requires a precise sensor. What are you using?

AS504B over I2C. It’s 14 bit so I expect decent resolution out of it. I have a second sensor connected too, so if it’s necessary I may be able get the reading from both and apply the Vernier principle to get a more accurate reading.

Hey @wocsor,
The sensing precision should be fine. but there might be some issues of alignment. Can you show us a short video of what happens when the motor locks up.

The voltage control mode is more or less the torque control. And the motor should spin proportionally to the voltage that you set. So the motor should turn.

Can you show us the code you are using also, it will help to debug!
I am very interested to help you with this one, I am really happy with the stepper motor results. :smiley:

I have only used nema17s but there will not bee too much difference for the other ones as well.

The issue might be Arudino UNO though, the precision of hte PWM and I2C reading performance. If this proves to be a problem, you might consider some more capable MCUs. But it should be enough to start.

1 Like

Video of using the find_pole_pairs_number sketch and setting voltage to 4V:
https://youtu.be/SsKMOg_EgpY

The only changes I made are the 4.5 scalar added to getAngle(), changed BLDCMotor to SteooerMotor(50) and StepperDriver4PWM. I also removed the Encoder interrupt setting and set that to MagneticSensorI2C. I’ll post the sketch from my other PC in a sec

full sketch code:

/**
 * Utility arduino sketch which finds pole pair number of the motor
 * 
 * To run it just set the correct pin numbers for the BLDC driver and sensor CPR value and chip select pin.
 * 
 * The program will rotate your motor a specific amount and check how much it moved, and by doing a simple calculation calculate your pole pair number.
 * The pole pair number will be outputted to the serial terminal. 
 * 
 * If the pole pair number is well estimated your motor will start to spin in voltage mode with 2V target. 
 * 
 * If the code calculates negative pole pair number please invert your motor connector.
 * 
 * Try running this code several times to avoid statistical errors. 
 * > But in general if your motor spins, you have a good pole pairs number.  
 */
#include <SimpleFOC.h>

// BLDC motor instance
// its important to put pole pairs number as 1!!!
//BLDCMotor motor = BLDCMotor(1);
//BLDCDriver3PWM driver = BLDCDriver3PWM(9, 5, 6, 8);
// Stepper motor instance
// its important to put pole pairs number as 1!!!
StepperMotor motor = StepperMotor(50);
StepperDriver4PWM driver = StepperDriver4PWM(9, 6, 5, 3, 7, 4);

//  Encoder(int encA, int encB , int cpr, int index)
//Encoder encoder = Encoder(2, 3, 2048);
// interrupt routine intialisation
//void doA(){encoder.handleA();}
//void doB(){encoder.handleB();}

// magnetic sensor instance - SPI
//MagneticSensorSPI sensor = MagneticSensorSPI(10, 14, 0x3FFF);
// magnetic sensor instance - I2C
MagneticSensorI2C sensor = MagneticSensorI2C(0x42, 14, 0xFE, 8);
// magnetic sensor instance - analog output
// MagneticSensorAnalog sensor = MagneticSensorAnalog(A1, 14, 1020);

void setup() {

  // initialise magnetic sensor hardware
  sensor.init();
  // link the motor to the sensor
  motor.linkSensor(&sensor);

  // power supply voltage
  // default 12V
  driver.voltage_power_supply = 12;
  driver.init();
  motor.linkDriver(&driver);

  // initialize motor hardware
  motor.init();

  // monitoring port
  Serial.begin(115200);

  // pole pairs calculation routine
  Serial.println("Pole pairs (PP) estimator");
  Serial.println("-\n");

  float pp_search_voltage = 4; // maximum power_supply_voltage/2
  float pp_search_angle = 6*M_PI; // search electrical angle to turn
  
  // move motor to the electrical angle 0
  motor.controller = ControlType::angle_openloop;
  motor.voltage_limit=pp_search_voltage;
  motor.move(0);
  _delay(1000);
  // read the sensor angle 
  float angle_begin = sensor.getAngle();
  _delay(50);
  
  // move the motor slowly to the electrical angle pp_search_angle
  float motor_angle = 0;
  while(motor_angle <= pp_search_angle){
    motor_angle += 0.01;
    sensor.getAngle(); // keep track of the overflow
    motor.move(motor_angle);
  }
  _delay(1000);
  // read the sensor value for 180
  float angle_end = sensor.getAngle();
  _delay(50);
  // turn off the motor
  motor.move(0);
  _delay(1000);

  // calculate the pole pair number
  int pp = round((pp_search_angle)/(angle_end-angle_begin));

  Serial.print("Estimated PP : ");
  Serial.println(pp);
  Serial.println("PP = Electrical angle / Encoder angle ");
  Serial.print(pp_search_angle*180/M_PI);
  Serial.print("/");
  Serial.print((angle_end-angle_begin)*180/M_PI);
  Serial.print(" = ");
  Serial.println((pp_search_angle)/(angle_end-angle_begin));
  Serial.println();
   

  // a bit of monitoring the result
  if(pp <= 0 ){
    Serial.println("PP number cannot be negative");
    Serial.println(" - Try changing the search_voltage value or motor/sensor configuration.");
    return;
  }else if(pp > 30){
    Serial.println("PP number very high, possible error.");
  }else{
    Serial.println("If PP is estimated well your motor should turn now!");
    Serial.println(" - If it is not moving try to relaunch the program!");
    Serial.println(" - You can also try to adjust the target voltage using serial terminal!");
  }

  
  // set motion control loop to be used
  motor.controller = ControlType::voltage;
  // set the pole pair number to the motor
  motor.pole_pairs = pp;
  //align sensor and start FOC
  motor.initFOC();
  _delay(1000);

  Serial.println("\n Motor ready.");
  Serial.println("Set the target voltage using serial terminal:");
}

// uq voltage
float target_voltage = 2;

void loop() {

  // main FOC algorithm function
  // the faster you run this function the better
  // Arduino UNO loop  ~1kHz
  // Bluepill loop ~10kHz 
  motor.loopFOC();

  // Motion control function
  // velocity, position or voltage (defined in motor.controller)
  // this function can be run at much lower frequency than loopFOC() function
  // You can also use motor.move() and set the motor.target in the code
  motor.move(target_voltage);
  
  // communicate with the user
  serialReceiveUserCommand();
}


// 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_voltage = received_chars.toFloat();
      Serial.print("Target voltage: ");
      Serial.println(target_voltage);
      
      // reset the command buffer 
      received_chars = "";
    }
  }
}

here is the serial monitor output as well

Pole pairs (PP) estimator

Estimated PP : 1
PP = Electrical angle / Encoder angle
1080.00/1085.17 = 1.00

If PP is estimated well your motor should turn now!

  • If it is not moving try to relaunch the program!
  • You can also try to adjust the target voltage using serial terminal!

Motor ready.
Set the target voltage using serial terminal:
Target voltage: 5.00
Target voltage: 0.00

Hey @wocsor,

I think this is actually ok, you are not far from getting it to work.
First of all I think your encoder is pretty precisely set and it seems to work well.

The reason why you have the pole pair equal to 1 is because you have imputed the pole pair number on the top of your program:

// its important to put pole pairs number as 1!!!
StepperMotor motor = StepperMotor(50);
StepperDriver4PWM driver = StepperDriver4PWM(9, 6, 5, 3, 7, 4);

as the comment says the code relies on the fact that you put number 1 not 50 :smiley:

// its important to put pole pairs number as 1!!!
StepperMotor motor = StepperMotor(1);
StepperDriver4PWM driver = StepperDriver4PWM(9, 6, 5, 3, 7, 4);

But since you’ve got 1 as your result this means that 50 is a good number :smiley:

But can you try a simpler example like this one:

/**
 *  simple stepper example
 */
#include <SimpleFOC.h>

StepperMotor motor = StepperMotor(50);
StepperDriver4PWM driver = StepperDriver4PWM(9, 6, 5, 3, 7, 4);

// magnetic sensor instance - I2C
MagneticSensorI2C sensor = MagneticSensorI2C(0x42, 14, 0xFE, 8);

void setup() {

  // initialise magnetic sensor hardware
  sensor.init();
  // link the motor to the sensor
  motor.linkSensor(&sensor);

  // power supply voltage
  // default 12V
  driver.voltage_power_supply = 12;
  driver.init();
  motor.linkDriver(&driver);


  // monitoring port
  Serial.begin(115200);

  // set motion control loop to be used
  motor.controller = ControlType::voltage;

  motor.voltage_sensor_align = 1; //try different values

  // initialize motor hardware
  motor.init();
  //align sensor and start FOC
  motor.initFOC();
  _delay(1000);

  Serial.println("\n Motor ready.");
  Serial.println("Set the target voltage using serial terminal:");
}

// uq voltage
float target_voltage = 2;

void loop() {

  // main FOC algorithm function
  motor.loopFOC();

  // Motion control function
  motor.move(target_voltage);
  
  // communicate with the user
  serialReceiveUserCommand();
}


// 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_voltage = received_chars.toFloat();
      Serial.print("Target voltage: ");
      Serial.println(target_voltage);
      
      // reset the command buffer 
      received_chars = "";
    }
  }
}

I realized that I had set the pole pairs incorrectly for the sketch right after posting that too! Helps to RTFM haha. After setting it to 1, I am getting the approx 50 number in serial. Also, @Owen_Williams suggestion seems promising because once I remove the scalar and run it again, I get close to 225. I still cannot get full rotation though.

I uploaded your example and played with the voltage_sensor_align as mentioned, and I can get close to a half rotation of the motor but it now shakes violently back and forth. I have gone over the wiring and verified it but still no luck. I wonder what could be the issue… possibly some noise on the I2C lines? Arduino weakness? The only other micro I have available is a Teensy 3.6

When you upload the program, there is first a calibration procedure which takes few seconds (cca 10s). During this calibration your motor will move with 5 ticks in one direction and come back 5 ticks.
Is this what you mean that you can move the motor half foration?

The volatge control mode gives you the possibility to set an arbitraty volatge to the motor and by doing so your motor should start moving. If it desn’t then there is a problem of alignment somewhere or something similar. Does it move at all?
Does the l298n get hot?

Try using this code. It has a bit different magnetic sensor declaration and it has monitoring enabled. Can you show us the output of the serial terminal :smiley:

/**
 * 
 * Torque control example using voltage control loop.
 * 
 * Most of the low-end BLDC driver boards doesn't have current measurement therefore SimpleFOC offers 
 * you a way to control motor torque by setting the voltage to the motor instead hte current. 
 * 
 * This makes the BLDC motor effectively a DC motor, and you can use it in a same way.
 */
#include <SimpleFOC.h>


 Stepper motor & driver instance
StepperMotor motor = StepperMotor(50);
StepperDriver4PWM driver = StepperDriver4PWM(9, 5, 10, 6,  8);

MagneticSensorI2C sensor = MagneticSensorI2C(AS5048_I2C);


void setup() { 
  
  // initialize encoder sensor hardware
  sensor.init();
  // link the motor to the sensor
  motor.linkSensor(&sensor);

  // driver config
  // power supply voltage [V]
  driver.voltage_power_supply = 12;
  driver.init();
  // link driver
  motor.linkDriver(&driver);


  // aligning voltage
  motor.voltage_sensor_align = 1;

  // set motion control loop to be used
  motor.controller = ControlType::voltage;

  // use monitoring with serial 
  Serial.begin(115200);
  // comment out if not needed
  motor.useMonitoring(Serial);

  // initialize motor
  motor.init();
  // align sensor and start FOC
  motor.initFOC();

  Serial.println("Motor ready.");
  Serial.println("Set the target voltage using serial terminal:");
  _delay(1000);
}

// target voltage to be set to the motor
float target_voltage = 2;

void loop() {

  // main FOC algorithm function
  // the faster you run this function the better
  // Arduino UNO loop  ~1kHz
  // Bluepill loop ~10kHz 
  motor.loopFOC();

  // Motion control function
  // velocity, position or voltage (defined in motor.controller)
  // this function can be run at much lower frequency than loopFOC() function
  // You can also use motor.move() and set the motor.target in the code
  motor.move(target_voltage);
  
  // communicate with the user
  serialReceiveUserCommand();
}


// 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_voltage = received_chars.toFloat();
      Serial.print("Target voltage: ");
      Serial.println(target_voltage);
      
      // reset the command buffer 
      received_chars = "";
    }
  }
}

I made a few edits to reflect my setup. Here is the Serial output:

MOT: Monitor enabled!
MOT: Init variables.
MOT: Enable.
MOT: Align sensor.
MOT: natural_direction==CW
MOT: Absolute zero align.
MOT: Success!
MOT: Motor ready.
Motor ready.
Set the target voltage using serial terminal:
Target voltage: 4.00
Target voltage: -4.00
Target voltage: 5.00
Target voltage: 6.00
Target voltage: 7.00
Target voltage: 8.00
Target voltage: 0.00
Target voltage: -8.00
Target voltage: 0.00
Target voltage: 8.00
Target voltage: 0.00
Target voltage: -8.00
Target voltage: 0.00
Target voltage: 5.00
Target voltage: 0.00
Target voltage: 10.00
Target voltage: 0.00

I also took a video of this run:
https://youtu.be/pB8QSzZgg3A

The half turn happens when I set the voltage then go negative, but it seems to go to a position and stick there / vibrate in place if I increase the voltage.

Huh, cann you add to your loop function line: motor.monitor();
And show us the output.

What is your backlash situation?
If there is any backlash this might be a problem. For foc control it is really important to have a sensor and the motor rigidly connected.

Also are you sure that the gear ratio is exactly 4.5. if it is 4.6 or 4.4 it will probably not work also.

backlash on these is fairly low, although it is driving through 2 gears to get to the sensor. The ratio based on the teeth is correct, though (20T -> 40T – 20T same axle -> 45T sensor gear). Here is the output from Serial with me increasing the voltage up to 8. Also I have a bigger heatsink and a fan on the L298N to prevent it from overheating while I test

Weirdy, if I push against the sensor gear, it feels like it’s trying to “assist” by turning the motor in the same direction.

Update: the previous paste had a scale of 4.5 applied to the velocity function by accident. Here is the serial output with that fixed:

It still shakes violently when i increase the voltage instead of just turning like it should though

Hey @wocsor,

If 1 rotation for the sensor is 4.5 rotations of the motor then we have to divide getAngle by 4.5 and not multiply. :smiley:

What we need to receive in the shaftAngle has to be motor position. And that is whatever sensor reads divided by 4.5.

Am I forgetting something? :slight_smile:

No dice, it’s no longer chaotically vibrating but now it’s just holding there instead. For giggles, I tested the open loop again and it still works fine with my wiring change I made earlier.

Thanks again by the way, I know this can be tedious without having proper access to the setup in person!

So I printed a holder for this encoder and fixed it to the back of the motor, and retried everything from stock. It works great now! There’s a bit of “cogging” when I try backdriving the motor and it’s being run. I suppose I should implement some kind of override if the voltage increases over a certain threshold.

Thanks!

1 Like

If running position or velocity control this should make sure you stay within a set voltage limit.

motor.voltage_limit = voltageLimit;