Closed Looped Stepper Motor Error correction issue

Hello all,
This is my first post so please forgive me if I do not get the proper etiquette right for this forum.
I am working on a goto system for my dobsonian telescope. I have two encoders, one for Azimuth and one for Elevation. My code is far from complete but I am currently experimenting with getting my stepper motors to be closed looped. I currently only have the Azimuth encoder and Stepper motor wired up so some of the code is missing for the Elevation axis.
For my current setup I have everything on my workbench not mechanically connected, just wired up.
For my test I have an initial Azimuth position demand set for 90 degrees in the setup as:
long AziPositiondemand = 90;

I then have the Error calculated from the position demand and the encoder position:
Error = (Azi.read()*0.15)-AziPositiondemand;

Next I have the stepper motor move the amount of steps needed to correct the error. When I power the Arduino on the Stepper will move the calculated steps then stop. If I had it setup correctly it should continue to move until the error is zero but it doesn’t. It only moves the initial calculated amount. If I turn the encoder it will recalculate and move that amount. I want the stepper to keeping turning until the encoder is at the demand position/Error = 0.

(For clarity the encoder and stepper are not connected so its impossible for it to reach the demand position. They are just sitting next to each on the bench)

Thank you for any help,
Zak

#include <AccelStepper.h>
#include <MultiStepper.h>
#include <LiquidCrystal.h> // includes the LiquidCrystal Library
#include <Encoder.h>

// Change these pin numbers to the pins connected to your encoder.
// Best Performance: both pins have interrupt capability
// Good Performance: only the first pin has interrupt capability
// Low Performance: neither pin has interrupt capability
// Two phase 4 frequency doubling to 2400 pulses
// 2400 * 0.15 = 360
const char degree = char (223);
Encoder Azi(18, 19);
Encoder Elev(20, 21);
// avoid using pins with LEDs attached

// Define pin connections
const int dirPin = 22;
const int stepPin = 24;

// Define motor interface type
#define motorInterfaceType 1

// Creates an instance
AccelStepper myStepper(motorInterfaceType, stepPin, dirPin);

LiquidCrystal lcd(1, 2, 4, 5, 6, 7); // Creates an LC object. Parameters: (rs, enable, d4, d5, d6, d7)
const short setZero = 10;

void setup() {

pinMode(setZero, INPUT_PULLUP);
digitalWrite(setZero, LOW);
lcd.begin(16,2); // Initializes the interface to the LCD screen, and specifies the dimensions (width and height) of the display }
// Declare pins as Outputs
pinMode(stepPin, OUTPUT);
pinMode(dirPin, OUTPUT);

// set the maximum speed, acceleration factor,
// initial speed and the target position
myStepper.setMaxSpeed(10000);
myStepper.setAcceleration(500);
myStepper.setSpeed(2000);
}

void loop() {

long AziPositiondemand = 90;
long Error = 0;

Error = (Azi.read()*0.15)-AziPositiondemand;
if (Azi.read() >= 2400.0) { //If you rotate past +360 Degs it will reset to zero
Azi.write(0);
}
if (Azi.read() <= -2400.0) { //If you rotate past -360 Degs it will reset to zero
Azi.write(0);
}
if (Elev.read() >= 2400.0) { //If you rotate past +360 Degs it will reset to zero
Elev.write(0);
}
if (Elev.read() <= -2400.0) { //If you rotate past -360 Degs it will reset to zero
Elev.write(0);
}
{
lcd.setCursor(0,0); // Sets the location at which subsequent text written to the LCD will be displayed
lcd.print(“Azimuth=”); // Prints “Azimuth” on the LCD
lcd.setCursor(9,0);
lcd.print(Azi.read()0.15); //0.15 conversion factor of encoder pulses to degrees
lcd.setCursor(15,0);
lcd.print(degree);
lcd.setCursor(0,1);
lcd.print(“Elevation=”);
lcd.print(Elev.read()0.15); //0.15 conversion factor of encoder pulses to degrees
lcd.setCursor(15,1);
lcd.print(degree);
}
if (digitalRead(setZero) == HIGH){ //Pushbutton that Zeros Azimuth
Azi.write(0);
}
if (Error == 0) { //This section reads the Error between the encoder and the Demand and moves stepper to eliminate error
myStepper.stop();}
if (Error > 0) {
myStepper.moveTo(-Error
1.11); // 1.11 is conversion factor for degrees to steps
myStepper.run();
}
if (Error < 0) {
myStepper.moveTo(Error
1.11); //1.11 is conversion factor for degrees to steps
myStepper.run();
}

}

Digital_Setting_Circle_Code_with_LCD_and_Stepper_Azimuth_test.i.ino (3.1 KB)

Hello, it would good if you can put your code in code tags(the </> button, upper-left corner of text editor) so others can see your code better :smiley:

#include <AccelStepper.h>
#include <MultiStepper.h>
#include <LiquidCrystal.h> // includes the LiquidCrystal Library
#include <Encoder.h>

// Change these pin numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability
//   Two phase 4 frequency doubling to 2400 pulses
//   2400 * 0.15 = 360
const char degree = char (223);
Encoder Azi(18, 19);
Encoder Elev(20, 21);
//   avoid using pins with LEDs attached

// Define pin connections
const int dirPin = 22;
const int stepPin = 24;

// Define motor interface type
#define motorInterfaceType 1

// Creates an instance
AccelStepper myStepper(motorInterfaceType, stepPin, dirPin);

LiquidCrystal lcd(1, 2, 4, 5, 6, 7); // Creates an LC object. Parameters: (rs, enable, d4, d5, d6, d7)
const short setZero = 10;



void setup() {
 
  pinMode(setZero, INPUT_PULLUP);
  digitalWrite(setZero, LOW);
  lcd.begin(16,2); // Initializes the interface to the LCD screen, and specifies the dimensions (width and height) of the display }
  // Declare pins as Outputs
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);
 
  // set the maximum speed, acceleration factor,
  // initial speed and the target position
  myStepper.setMaxSpeed(10000);
  myStepper.setAcceleration(500);
  myStepper.setSpeed(2000);
    }

void loop() {
 
  long AziPositiondemand = 90;
  long Error = 0;
 
  Error = (Azi.read()*0.15)-AziPositiondemand;
  if (Azi.read() >= 2400.0) {  //If you rotate past +360 Degs it will reset to zero
    Azi.write(0);
  }
   if (Azi.read() <= -2400.0) {  //If you rotate past -360 Degs it will reset to zero
    Azi.write(0);
   }
   if (Elev.read() >= 2400.0) {  //If you rotate past +360 Degs it will reset to zero
    Elev.write(0);
  }
   if (Elev.read() <= -2400.0) {  //If you rotate past -360 Degs it will reset to zero
    Elev.write(0);
   }
     {
     lcd.setCursor(0,0); // Sets the location at which subsequent text written to the LCD will be displayed
     lcd.print("Azimuth="); // Prints "Azimuth" on the LCD
     lcd.setCursor(9,0);
     lcd.print(Azi.read()*0.15); //0.15 conversion factor of encoder pulses to degrees
     lcd.setCursor(15,0);
     lcd.print(degree);
     lcd.setCursor(0,1);
     lcd.print("Elevation=");
     lcd.print(Elev.read()*0.15); //0.15 conversion factor of encoder pulses to degrees
     lcd.setCursor(15,1);
     lcd.print(degree);
    }
    if (digitalRead(setZero) == HIGH){ //Pushbutton that Zeros Azimuth
      Azi.write(0);
    }
    if (Error == 0) { //This section reads the Error between the encoder and the Demand and moves stepper to eliminate error
       myStepper.stop();}
    if (Error > 0) {
         myStepper.moveTo(-Error*1.11); // 1.11 is conversion factor for degrees to steps
         myStepper.run();
        }
    if (Error < 0) {
         myStepper.moveTo(Error*1.11);  //1.11 is conversion factor for degrees to steps
         myStepper.run();
        }
       
}

What is the purpose of the variable long AziPositiondemand = 90;

You are not printing the value of the variable Error - maybe the program is doing what it should.

Mixing integers and floating point values like this probably won’t work

Error = (Azi.read()*0.15)-AziPositiondemand;

A microprocessor does not do maths like a calculator. It probably treats 0.15 as zero.

As your encoder and your stepper motor both work in integers it would be best to stick to integer maths. 0.15 is the same as 15 / 100 so try

Error = (Azi.read() * 15 / 100)-AziPositiondemand;

…R

Robin2:
What is the purpose of the variable long AziPositiondemand = 90;

You are not printing the value of the variable Error - maybe the program is doing what it should.

This portion will eventually be replaced by a database. You will select a target and a azimuth and elevation coordinates will be demanded. I did put a serial.print in the code to see if the error was changing and it wasn`t. It stilled showed an error of 90 degrees. I left the serial port out of the code because it made the LCD go crazy with gibberish. I am not sure what thats about.

Robin2:
Mixing integers and floating point values like this probably won't work
Code: [Select]

Error = (Azi.read()*0.15)-AziPositiondemand;

A microprocessor does not do maths like a calculator. It probably treats 0.15 as zero.

As your encoder and your stepper motor both work in integers it would be best to stick to integer maths. 0.15 is the same as 15 / 100 so try
Code: [Select]

Error = (Azi.read() * 15 / 100)-AziPositiondemand;

It seems to deal with these fine. I tried like you suggested and the readout no longer had decimal place accuracy.

How many degrees / pulses is one motor step?

zbridges:
It seems to deal with these fine. I tried like you suggested and the readout no longer had decimal place accuracy.

If you are dealing with integer numbers of encoder pulses and integer numbers of motor steps the concept of decimal place accuracy is surely irrelevant. The motor can't move a fraction of a step.

...R

Robin2:
If you are dealing with integer numbers of encoder pulses and integer numbers of motor steps the concept of decimal place accuracy is surely irrelevant. The motor can’t move a fraction of a step.

…R

I understand what your saying but its not what I am seeing. When I have the code using the 0.15 conversion factor I have really high fidelity. I can barely touch the shaft encoder and it reads a change down to decimals. Your saying that lcd.print(Azi.read()*0.15); //0.15 conversion factor of encoder pulses to degrees should really only put out zero because the arduino would only see that 0.15 as zero which in turn be multiplied with the pulses giving only zero? Thats not what I am experiencing. I attached photos showing what I am seeing.

JCA34F:
How many degrees / pulses is one motor step?

I have the stepper driver set at half step right now so 1 step is 0.9 degrees.

zbridges:
I understand what your saying but its not what I am seeing.

I did a test on my Uno with this short piece of code

void setup() {
    Serial.begin(500000);
    Serial.println("<Starting  >");

    long aziRead = 1032;

    long AziPositiondemand = 90;
    long Error = 0;
    long Error2 = 0;

    Error = (aziRead * 0.15) - AziPositiondemand;
    Error2 = (aziRead * 15 / 100) - AziPositiondemand;
    Serial.println(Error);
    Serial.println(Error2);

}

void loop() {

}

And, contrary to what I was saying the calculation does work with 0.15 - I learn stuff every day! But I get exactly the same result with 15 / 100. Personally I would use 15 / 100.

Note that I used the variable aziRead because I don’t have the encoder library or an encoder.

…R

I changed the code to simply:

 if (Error == 0) { //This section reads the Error between the encoder and the Demand and moves stepper to eliminate error
       myStepper.stop();}
    if (Error > 0) {
         myStepper.runSpeed(); 
         myStepper.run();
        }
    if (Error < 0) {
         myStepper.runSpeed();  
         myStepper.run();
        }

And this halfway works. The stepper will run continuously until I turn the encoder to the demand position. I say this only halfway works because the runSpeed() will only turn the stepper one direction. I cant put a negative in it to make it turn the other way if it over shoots. It will have to come all the way back around. Thats no good. Plus, I am giving up all the nice features of using the accelstepper libary such as acceleration/deceleration which I want to keep.

On another note, whenever I put the serial.begin in the code my lcd goes bonkers.It only displays gibberish when I dont have the serial monitor open and when I do have the serial monitor open its blank. Any advice on that?

zbridges:
On another note, whenever I put the serial.begin in the code my lcd goes bonkers.It only displays gibberish when I dont have the serial monitor open and when I do have the serial monitor open its blank. Any advice on that?

I figured this part out. I changed the 1, 2 pins for the lcd. Serial monitor doesnt like to share these pins.

LiquidCrystal lcd(32, 34, 4, 5, 6, 7); // Creates an LC object. Parameters: (rs, enable, d4, d5, d6, d7)

Now the lcd display works along with the serial monitor.

If 1 motor step = 6 encoder steps, setting target position to anything besides an even multiple of 6 won't work because the motor cannot get there.

zbridges:
I cant put a negative in it to make it turn the other way if it over shoots.

Why not?

...R

Robin2:
Why not?

...R

I get this error when I put a negative in the parenthesis
Arduino: 1.8.10 (Windows 10), Board: "Arduino/Genuino Mega or Mega 2560, ATmega2560 (Mega 2560)"

C:\Users\admin\Desktop\Digital_Setting_Circle_Code_with_LCD_and_Stepper_Azimuth_test.i\Digital_Setting_Circle_Code_with_LCD_and_Stepper_Azimuth_test.i.ino: In function 'void loop()':

Digital_Setting_Circle_Code_with_LCD_and_Stepper_Azimuth_test.i:90:30: error: expected primary-expression before ')' token

myStepper.runSpeed(-);

^

Multiple libraries were found for "Encoder.h"
Used: d:\Documents\Arduino\libraries\Encoder
Multiple libraries were found for "AccelStepper.h"
Used: d:\Documents\Arduino\libraries\AccelStepper
Multiple libraries were found for "LiquidCrystal.h"
Used: C:\Program
exit status 1
expected primary-expression before ')' token

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

Robin2:
Why not?

…R

I figured out a solution. I moved the setSpeed to each if statement and made that negative:

  if (Error == 0) { //This section reads the Error between the encoder and the Demand and moves stepper to eliminate error
       myStepper.stop();}
    if (Error > 0) {
        myStepper.setSpeed(-2000);
         myStepper.runSpeed();  
         myStepper.run();
        }
    if (Error < 0) {
        myStepper.setSpeed(2000);
         myStepper.runSpeed();  
         myStepper.run();
        }

This isnt ideal but it will work for now.
I still do not understand why my original code would not work properly.

zbridges:
This isnt ideal but it will work for now.

Why not? - it looks like the normal way to do it.

...R

Robin2:
Why not? - it looks like the normal way to do it.

...R

Im not getting the full benefit of the accelstepper libary. No acceleration or deceleration and such.

zbridges:
Im not getting the full benefit of the accelstepper libary. No acceleration or deceleration and such.

I missed the fact that you have these two lines

         myStepper.runSpeed();  
         myStepper.run();

If you want acceleration you should only use run(). HOWEVER the AccelStepper library needs to know how many steps it is required to move in order to calculate the acceleration and deceleration. You could probably trick it by telling it to move a very large number of steps (many more than you need) as that will cause the acceleration to work. Then if you use the stop() function when you actually want to stop it will work out the deceleration to bring the motor to an orderly stop.

Note also that the run() function needs to be called very frequently - more frequently than the desired step rate - so it is usually just placed in the loop() function.

If this does not meet your need then it is not all that difficult to write your own acceleration code. This Simple acceleration code should give you some ideas.

...R