Stepper Motor Position Control Works - Speed and Acceleration Doesn't?

All,

I’ve made a position controller for two stepper motors (for x & y angular positioning of a device). It’s based on a single Arduino Nano. Each motor position (rotation angle) is controlled by a separate rotary encoder that can be turned incrementally by hand.

Each step of the encoder knob corresponds to a number of definable steps on the appropriate motor. Pressing the encoder changes the rate (number of steps the motor turns) by a scaling factor set in the code.

These aspects of the controller work fine, and I can easily independently tune each motor to drive two different positioning mechanisms identical x & y angles per encoder step (despite one being via a friction drive, the other via a leadscrew).

The probelm is that I assumed that I would also be able to easily control the speed and acceleration of each motor as it moves from one position to another, by changing the vales for, for example:

motor1.setMaxSpeed
motor1.setAccelerationMax

However changing these values has no effect on speeding up the motors. To an extent it works for lower values, but is inconsistent (I need significantly higher speeds for motor 1), and sometimes acceleration of a motor seems to have a distinct step in it rather than being completely smooth. If I double the speed and acceleration for Motor 1 and upload the revised code, it just stays at the default value form the previous code.

I have generated code using AI, since this was the quickest way to get a workable result. however I’m now stuck.

Could anyone give advice how to solve this issue? A knowlegable friend fo mine has helped me with sorting another separate issue out on this, and reckons it could be something to do with the stepper library.

In all honesty what I need is a help with getting revised code I could copy and paste, since at my current skill level I’m unable to write the code from descriptions of general advice.

Appreciate any help because it’s been a long project, and this is the final hurdle to overcome.

Thanks.

#include <Wire.h>
#include <Adafruit_seesaw.h>
#include <AccelStepper.h>

// Create seesaw objects for two encoders
Adafruit_seesaw encoder1;
Adafruit_seesaw encoder2;

// Define TMC2209 pins for Motor 1
#define DIR_PIN1 2    
#define STEP_PIN1 3   

// Define TMC2209 pins for Motor 2
#define DIR_PIN2 6    
#define STEP_PIN2 7   

// Define scaling factors for encoder to motor steps
int stepsPerEncoderStep1; 
int stepsPerEncoderStep2;

// High and low step scaling factors
const int HIGH_STEP_COUNT = 20; 
const int LOW_STEP_COUNT = 1;

//binary state of adjustment pitch, true for coarse, false for fine
bool motorOneCoarse = true;
bool motorTwoCoarse = true;

//scale the indidivdual motors here
const int MOTOR_ONE_SCALE = 80;  //Altitude factor
const int MOTOR_TWO_SCALE = 4;  //Azimuth factor

int16_t lastEncoderPosition1 = 0;
int16_t lastEncoderPosition2 = 0;

// Create AccelStepper objects for two motors
AccelStepper motor1(AccelStepper::DRIVER, STEP_PIN1, DIR_PIN1);
AccelStepper motor2(AccelStepper::DRIVER, STEP_PIN2, DIR_PIN2);

void setup() {
  // Initialize serial for debugging
  Serial.begin(115200);

  // Initialize I2C for encoder 1
  if (!encoder1.begin(0x36)) {
    Serial.println("Encoder 1 not found!");
    while (1); 
  }
  encoder1.pinMode(24, INPUT_PULLUP); // Set push-button pin for encoder 1
  
  // Initialize I2C for encoder 2
  if (!encoder2.begin(0x37)) { // Assuming encoder 2 is on address 0x37
    Serial.println("Encoder 2 not found!");
    while (1); 
  }
  encoder2.pinMode(24, INPUT_PULLUP); // Set push-button pin for encoder 2

  // Configure AccelStepper objects
  motor1.setMaxSpeed(1000);  // Maximum speed (steps per second)
  motor1.setAcceleration(500);  // Acceleration (steps per second^2)
  
  motor2.setMaxSpeed(1000); 
  motor2.setAcceleration(500);

  // Start at position 0 for both encoders
  lastEncoderPosition1 = encoder1.getEncoderPosition();
  lastEncoderPosition2 = encoder2.getEncoderPosition();
}

void loop() {
  // Read button states to toggle scaling factors

  if (motorOneCoarse)
  {
    //if in coarse adjustment, calculate total scaling factor by multiplying the initial scale by the high multiplier
    stepsPerEncoderStep1 = (MOTOR_ONE_SCALE * HIGH_STEP_COUNT);
  }
  else
  {
    //if in fine adjustment, calculate total scaling factor by multiplying the initial scale by the low multiplier
    stepsPerEncoderStep1 = (MOTOR_ONE_SCALE * LOW_STEP_COUNT);
  }

  if (motorTwoCoarse)
  {
    //if in coarse adjustment, calculate total scaling factor by multiplying the initial scale by the high multiplier
    stepsPerEncoderStep2 = (MOTOR_TWO_SCALE * HIGH_STEP_COUNT);
  }
  else
  {
    //if in fine adjustment, calculate total scaling factor by multiplying the initial scale by the low multiplier
    stepsPerEncoderStep2 = (MOTOR_TWO_SCALE * LOW_STEP_COUNT);
  }

  if (encoder1.digitalRead(24) == LOW) { // Button pressed on encoder 1
    motorOneCoarse = !motorOneCoarse;
    delay(300); // Debounce
  }

  if (encoder2.digitalRead(24) == LOW) { // Button pressed on encoder 2
    motorTwoCoarse = !motorTwoCoarse;
    delay(300); // Debounce
  }

  // Read positions from encoders
  int16_t encoderPosition1 = encoder1.getEncoderPosition();
  int16_t encoderPosition2 = encoder2.getEncoderPosition();

  // Calculate steps to move for Motor 1
  int stepsToMove1 = (encoderPosition1 - lastEncoderPosition1) * stepsPerEncoderStep1;
  if (stepsToMove1 != 0) {
    motor1.move(stepsToMove1);  // Set target position
    lastEncoderPosition1 = encoderPosition1;
  }

  // Calculate steps to move for Motor 2
  int stepsToMove2 = (encoderPosition2 - lastEncoderPosition2) * stepsPerEncoderStep2;
  if (stepsToMove2 != 0) {
    motor2.move(stepsToMove2);  // Set target position
    lastEncoderPosition2 = encoderPosition2;
  }

  // Run motors to their target positions
  motor1.run();
  motor2.run();

  // Debugging information
  Serial.print("Encoder 1 Position: ");
  Serial.println(encoderPosition1);
  Serial.print("Steps for Motor 1: ");
  Serial.println(stepsToMove1);

  Serial.print("Encoder 2 Position: ");
  Serial.println(encoderPosition2);
  Serial.print("Steps for Motor 2: ");
  Serial.println(stepsToMove2);
}

Did you include the acceleration and speed parameters when you asked AI?

Hello,

Not initially, becasue I thought by default they would be included as variable parameters (as they were in another code for the same project for pure speed control).

I asked AI to retrospectively add speed and acceleration variables, rather than maximum values, but this resulted in the motors running continuously at the set speeds rather than moving to incremental positions at a defined speed.

Thanks.

I think your main problem is the cycle time of your loop(). The run() function of AccelStepper creates one step at max when it is called. So your sketch cannot create more than 1 step per cycletime. And this seems fairly low. There are even delay() in your sketch, a lot of I2C communication, and serial prints in every loop. All of this slows down your cycle time.
You could try my MobaTools library in stead of AccelStepper. The MoToStepper class creates the steps via timer interrupt in the background, so this is independent of loop() cycle time, and even delay() isn't a problem.

Is this a 'classic' Nano? There are a lot of very different boards called 'Nano'.

In a first step remove all your Serial.print() statements and see if your steppers run faster.

MicroBahner, thanks for the comments. As mentioned I’m using AI, and an insignificant amount of Arduino programming knowledge, so it’s very unlikely I could start substituting AccelStepper for something else.

I just removed the Serial statements, but this had no effect on motor speed whan I adjusted the values.

The board is as per the image that has hopefully uploaded here:

And here is the box that I’ve made with the rest of the stuff:

I had problems controlling three motors (two for position and one for continuous drive) with one board, so I just used two boards as above. Everything else works fine apart from the speed-to-position control, but unfortunately it’s something I really need to get right.

I’m actually amazed that something so outwardly simple has caused so many issues.

Thanks.

That shouldn't be too difficult. Because you use a very special HW, I cannot test it, but you could try this (at least it compiles without error).
I simply replaced the AccelStepper commands with the corresponding MobaTools commands:

#include <Wire.h>
#include <Adafruit_seesaw.h>
#include <MobaTools.h>

// Create seesaw objects for two encoders
Adafruit_seesaw encoder1;
Adafruit_seesaw encoder2;

// Define TMC2209 pins for Motor 1
#define DIR_PIN1 2    
#define STEP_PIN1 3   

// Define TMC2209 pins for Motor 2
#define DIR_PIN2 6    
#define STEP_PIN2 7   

// Define scaling factors for encoder to motor steps
int stepsPerEncoderStep1; 
int stepsPerEncoderStep2;

// High and low step scaling factors
const int HIGH_STEP_COUNT = 20; 
const int LOW_STEP_COUNT = 1;

//binary state of adjustment pitch, true for coarse, false for fine
bool motorOneCoarse = true;
bool motorTwoCoarse = true;

//scale the indidivdual motors here
const int MOTOR_ONE_SCALE = 80;  //Altitude factor
const int MOTOR_TWO_SCALE = 4;  //Azimuth factor

int16_t lastEncoderPosition1 = 0;
int16_t lastEncoderPosition2 = 0;

// Create MoToStepper objects for two motors
MoToStepper motor1(200); // 200 = steps/rev (may be need to adjust if microsteps are used )
MoToStepper motor2(200);

void setup() {
  // Initialize serial for debugging
  Serial.begin(115200);

  // Initialize I2C for encoder 1
  if (!encoder1.begin(0x36)) {
    Serial.println("Encoder 1 not found!");
    while (1); 
  }
  encoder1.pinMode(24, INPUT_PULLUP); // Set push-button pin for encoder 1
  
  // Initialize I2C for encoder 2
  if (!encoder2.begin(0x37)) { // Assuming encoder 2 is on address 0x37
    Serial.println("Encoder 2 not found!");
    while (1); 
  }
  encoder2.pinMode(24, INPUT_PULLUP); // Set push-button pin for encoder 2

  // Configure MoToStepper objects

  motor1.attach(STEP_PIN1, DIR_PIN1);
  motor1.setSpeedSteps(10000);  // Maximum speed (steps per second*10)
  motor1.setRampLen(500);  // Length of Acceleration ramp in Steps
  
  motor2.attach(STEP_PIN2, DIR_PIN2);
  motor1.setSpeedSteps(10000);  // Maximum speed (steps per second*10)
  motor1.setRampLen(500);  // Length of Acceleration ramp in Steps
  

  // Start at position 0 for both encoders
  lastEncoderPosition1 = encoder1.getEncoderPosition();
  lastEncoderPosition2 = encoder2.getEncoderPosition();
}

void loop() {
  // Read button states to toggle scaling factors

  if (motorOneCoarse)
  {
    //if in coarse adjustment, calculate total scaling factor by multiplying the initial scale by the high multiplier
    stepsPerEncoderStep1 = (MOTOR_ONE_SCALE * HIGH_STEP_COUNT);
  }
  else
  {
    //if in fine adjustment, calculate total scaling factor by multiplying the initial scale by the low multiplier
    stepsPerEncoderStep1 = (MOTOR_ONE_SCALE * LOW_STEP_COUNT);
  }

  if (motorTwoCoarse)
  {
    //if in coarse adjustment, calculate total scaling factor by multiplying the initial scale by the high multiplier
    stepsPerEncoderStep2 = (MOTOR_TWO_SCALE * HIGH_STEP_COUNT);
  }
  else
  {
    //if in fine adjustment, calculate total scaling factor by multiplying the initial scale by the low multiplier
    stepsPerEncoderStep2 = (MOTOR_TWO_SCALE * LOW_STEP_COUNT);
  }

  if (encoder1.digitalRead(24) == LOW) { // Button pressed on encoder 1
    motorOneCoarse = !motorOneCoarse;
    delay(300); // Debounce
  }

  if (encoder2.digitalRead(24) == LOW) { // Button pressed on encoder 2
    motorTwoCoarse = !motorTwoCoarse;
    delay(300); // Debounce
  }

  // Read positions from encoders
  int16_t encoderPosition1 = encoder1.getEncoderPosition();
  int16_t encoderPosition2 = encoder2.getEncoderPosition();

  // Calculate steps to move for Motor 1
  int stepsToMove1 = (encoderPosition1 - lastEncoderPosition1) * stepsPerEncoderStep1;
  if (stepsToMove1 != 0) {
    motor1.move(stepsToMove1);  // Set target position
    lastEncoderPosition1 = encoderPosition1;
  }

  // Calculate steps to move for Motor 2
  int stepsToMove2 = (encoderPosition2 - lastEncoderPosition2) * stepsPerEncoderStep2;
  if (stepsToMove2 != 0) {
    motor2.move(stepsToMove2);  // Set target position
    lastEncoderPosition2 = encoderPosition2;
  }

  // Run motors to their target positions ( not necessary with MobaTools
  //motor1.run();
  //motor2.run();

  // Debugging information
  Serial.print("Encoder 1 Position: ");
  Serial.println(encoderPosition1);
  Serial.print("Steps for Motor 1: ");
  Serial.println(stepsToMove1);

  Serial.print("Encoder 2 Position: ");
  Serial.println(encoderPosition2);
  Serial.print("Steps for Motor 2: ");
  Serial.println(stepsToMove2);
}

In my opinion you need programming experience when using AI. Sometimes it creates really crap or - even worse - subtle errors. You have to be able to understand what AI produces in order to recognize this.

N.B. MobaTools can be installed via the library manager.

[Edit:] You may need to adjust the ramp length to get better results. The ramplength defines the acceleration ( in steps from standstill to selected speed ).

Thanks very much - I will try this later on and report back.

Appreciate your help.

BTW what did you mean by very special HW? It’s just NEMA 17 stepper motors and drives, rotary encoders and an Arduino. I thought it was all very simple stuff?

I tried the code, but unfortunately neither motor moves when the encoders are adjusted. The motors are energised, just no movement.

I think the default microsteping is 1/8, so I set the code to 1600 steps. Neither 1600 or 200 worked.

Thanks.

I guess the second bit of this should all be “Motor 2”?

// Configure MoToStepper objects

  motor1.attach(STEP_PIN1, DIR_PIN1);
  motor1.setSpeedSteps(10000);  // Maximum speed (steps per second*10)
  motor1.setRampLen(500);  // Length of Acceleration ramp in Steps
  
  motor2.attach(STEP_PIN2, DIR_PIN2);
  motor1.setSpeedSteps(10000);  // Maximum speed (steps per second*10)
  motor1.setRampLen(500);  // Length of Acceleration ramp in Steps

I changed it, but still no movement.

Also tried changing speeds and ramp values.

Yes, of course, sorry.

If you try this, does it print the steps to do if you turn your encoder?

#include <Wire.h>
#include <Adafruit_seesaw.h>
#include <MobaTools.h>

// Create seesaw objects for two encoders
Adafruit_seesaw encoder1;
Adafruit_seesaw encoder2;

// Define TMC2209 pins for Motor 1
#define DIR_PIN1 2    
#define STEP_PIN1 3   

// Define TMC2209 pins for Motor 2
#define DIR_PIN2 6    
#define STEP_PIN2 7   

// Define scaling factors for encoder to motor steps
int stepsPerEncoderStep1; 
int stepsPerEncoderStep2;

// High and low step scaling factors
const int HIGH_STEP_COUNT = 20; 
const int LOW_STEP_COUNT = 1;

//binary state of adjustment pitch, true for coarse, false for fine
bool motorOneCoarse = true;
bool motorTwoCoarse = true;

//scale the indidivdual motors here
const int MOTOR_ONE_SCALE = 80;  //Altitude factor
const int MOTOR_TWO_SCALE = 4;  //Azimuth factor

int16_t lastEncoderPosition1 = 0;
int16_t lastEncoderPosition2 = 0;

// Steps the stepper should move
int stepsToMove1;
int stepsToMove2;

// Create MoToStepper objects for two motors
MoToStepper motor1(1600); // 200 = steps/rev (may be adjusted if microsteps are used )
MoToStepper motor2(1600);

void setup() {
  // Initialize serial for debugging
  Serial.begin(115200);

  // Initialize I2C for encoder 1
  if (!encoder1.begin(0x36)) {
    Serial.println("Encoder 1 not found!");
    while (1); 
  }
  encoder1.pinMode(24, INPUT_PULLUP); // Set push-button pin for encoder 1
  
  // Initialize I2C for encoder 2
  if (!encoder2.begin(0x37)) { // Assuming encoder 2 is on address 0x37
    Serial.println("Encoder 2 not found!");
    while (1); 
  }
  encoder2.pinMode(24, INPUT_PULLUP); // Set push-button pin for encoder 2

  // Configure MoToStepper objects

  motor1.attach(STEP_PIN1, DIR_PIN1);
  motor1.setSpeedSteps(10000);  // Maximum speed (steps per second*10)
  motor1.setRampLen(500);  // Length of Acceleration ramp in Steps
  
  motor2.attach(STEP_PIN2, DIR_PIN2);
  motor2.setSpeedSteps(10000);  // Maximum speed (steps per second*10)
  motor2.setRampLen(500);  // Length of Acceleration ramp in Steps
  

  // Start at position 0 for both encoders
  lastEncoderPosition1 = encoder1.getEncoderPosition();
  lastEncoderPosition2 = encoder2.getEncoderPosition();
}

void loop() {
  // Read button states to toggle scaling factors

  if (motorOneCoarse)
  {
    //if in coarse adjustment, calculate total scaling factor by multiplying the initial scale by the high multiplier
    stepsPerEncoderStep1 = (MOTOR_ONE_SCALE * HIGH_STEP_COUNT);
  }
  else
  {
    //if in fine adjustment, calculate total scaling factor by multiplying the initial scale by the low multiplier
    stepsPerEncoderStep1 = (MOTOR_ONE_SCALE * LOW_STEP_COUNT);
  }

  if (motorTwoCoarse)
  {
    //if in coarse adjustment, calculate total scaling factor by multiplying the initial scale by the high multiplier
    stepsPerEncoderStep2 = (MOTOR_TWO_SCALE * HIGH_STEP_COUNT);
  }
  else
  {
    //if in fine adjustment, calculate total scaling factor by multiplying the initial scale by the low multiplier
    stepsPerEncoderStep2 = (MOTOR_TWO_SCALE * LOW_STEP_COUNT);
  }

  if (encoder1.digitalRead(24) == LOW) { // Button pressed on encoder 1
    motorOneCoarse = !motorOneCoarse;
    delay(300); // Debounce
  }

  if (encoder2.digitalRead(24) == LOW) { // Button pressed on encoder 2
    motorTwoCoarse = !motorTwoCoarse;
    delay(300); // Debounce
  }

  // Read positions from encoders
  int16_t encoderPosition1 = encoder1.getEncoderPosition();
  int16_t encoderPosition2 = encoder2.getEncoderPosition();

  // Calculate steps to move for Motor 1
  stepsToMove1 = (encoderPosition1 - lastEncoderPosition1) * stepsPerEncoderStep1;
  if (stepsToMove1 != 0) {
    Serial.print("motor1 move"); Serial.println( stepsToMove1 );
    motor1.move(stepsToMove1);  // Set target position
    lastEncoderPosition1 = encoderPosition1;
  }

  // Calculate steps to move for Motor 2
  stepsToMove2 = (encoderPosition2 - lastEncoderPosition2) * stepsPerEncoderStep2;
  if (stepsToMove2 != 0) {
    Serial.print("motor2 move"); Serial.println( stepsToMove2 );
    motor2.move(stepsToMove2);  // Set target position
    lastEncoderPosition2 = encoderPosition2;
  }

  // Run motors to their target positions ( not necessary with MobaTools
  //motor1.run();
  //motor2.run();

  // Debugging information
  /*
  Serial.print("Encoder 1 Position: ");
  Serial.println(encoderPosition1);
  Serial.print("Steps for Motor 1: ");
  Serial.println(stepsToMove1);

  Serial.print("Encoder 2 Position: ");
  Serial.println(encoderPosition2);
  Serial.print("Steps for Motor 2: ");
  Serial.println(stepsToMove2);
  */
}

This could be where you start to increase you knowledge by writing your own code.
Moba Tools is a very good library and if you look at the examples you will learn a lot, especially as you have the author @MicroBahner in this thread.

To develop good robust code you need to do it on stages, chatGPT I would assume you told it what you wanted and it wrote it in one block, that makes debugging very difficult.

Note the fine print..

Tom.... :smiley: :+1: :coffee: :australia:

Thanks.

It was CoPilot not GPT. Yes, I described what I wanted, and it wrote the whole lot. After a few attempts it’s got me to where I am, with some additional help from a friend from work:

This was becasue initially there was a fault with the press-button on one of the axes not switching consistently between rates. As with the current issue, I barely understood the reason, never mind the solution.

Learning Arduio code is overwhelming to me, and since the objective is simply to get this device working and move on, I’ve little motivation to go through all the stuff I don’t need to know in order to learn the stuff I do need to know. I got an Elegoo starter kit and went though the tutorials, but most of them were irrelevant to my project, and it didn’t explain the subtleties of the code anyway.

I was told that what I’m doing is quite involved (despite it appearing to be very simple), so I assume I’d need a fairly in-depth knowledge of coding to solve these issues. That could take months, and frankly I don’t have the time or motivation to do it at the moment.

I think if someone could show me explicitly how to solve this problem and get it working, I’d be far more inclined to then go through the code in detail to fully understand how it works. At present I’m just thinking it seems a very elaborate and obstacle-strewn path to solving a simple actuation requirement.

If I can’t solve the issue I’ll have to put up with an imperfect system.

No problem. Thanks.

Using Serial Monitor, when I turn the encoders clockwise or anticlockwise, or push them* to change rate, it gives me this:

I don’t recognise or understand the characters, but every click of both encoders gives a new character.

ETA *not when the encoders are pushed - there is no response to that.

When your sketch uses 115200 baud, you need to select 115200 baud too in the Serial Monitor. Right now in your screenshot we can see its set to 9600 baud.

Hi, @g_n

Well AI needs to do things in stages, just like us humans do.

Ask it in stages if you have to, just get motor control, forget PID and the encoder.
Get that working, THEN, reading the encoder on its own.

That way you will also learn how to code and how to debug with CoPilot as your teacher.
It might feel that it takes longer, but you learn along the way and will have less frustrating failures.

Tom.... :smiley: :+1: :coffee: :australia:

If you don't want to learn how to program an Arduino, the Jobs and Paid Consultancy may be a better forum category for you.

Thanks for that - I changed the rate, and got this result:

So I guess that’s saying that the board is getting all the right information, ie that the dual-rates are working (1:20 for both encoders), and that the number of steps requested is also correct (1/8 microsteps, and 200 step motors, so 1600 microsteps for Motor 1 is 1 revolution of the motor, which is correct. Less for Motor 2).

But no motor movement.

Thanks Tom.

I did try that approach initially with this project, but found that once one part of the code was working, quite often when I requested an addition, it would add the addition, but also change the bit that was working such that it didn’t work anymore.

I found that very carefully writing the requirements in full gave a decent result, but as soon as any minor tweaks were requested, it would make silly mistakes (so silly that even I could spot them!). Eventually I’d get into a loop of making requests and then pointing out errors.

Thanks. I’ve got a pal at work who might help me out, I just thought I might be able to get it working here.

The issue I have is well demonstrated on this thread: There are some very experienced people here - yourself included, but the soultion clearly isn’t straightforward. It was the same with a previous question with a single Arduino not being able to cope with simultaneous speed control of one motor and position/speed control of two others. It didn’t get anywhere so I solved it myself by simply using two Arduinos. I think this is a related issue (timing).