Problem - Read from Closed Loop Stepper Motor while Rotating

Hi!

We have project of which the goal is to turn a wheel on a car, as accurately as possible, and hopefully fast enough. We therefore have a closed loop stepper motor (Nema 23) on which a gear is connected. This furthermore turns a 4x bigger wheel, that rotates the shaft on the steering wheel of the car.

The problem is that we are not able to measure the position of the stepper motor while we are turning it, e.g. turn it 360 degrees one way, then 360 degrees the other way.

The stepper motor is a Nema 23 Closed Loop (link under), and we are using a Teensy 3.6 as micro controller board, and Arduino IDE as code-platform.

The stepper motor has four output cables, (A+,A-,B+,B-) of which tall are connected to a stepper driver, that again is connected to the controller Teensy.

The stepper motor has an incremental encoder on the back of it, of which has 6 cables ( GRD, VCC, EA+, EA-,EB+, EB-). We put 5v on VCC, EA+ and EB+ as output ( pin5 and 6), and all the rest to ground. (maybe wrong here, what to with EA- and EB-?)

To read from the encoder we have created an interrupt-function that gets called every time EA+ rises from 0 til 1, and then we increase a global counting-variable that is supposed to measure actual rotation.

We have been successful in rotating the stepper motor, and successful in reading the position from the encoder, as we were rotating the stepper by hand, without voltage on the stepper.

The problem is that we are not able to do both things at the same time. As we are trying to Serial.print() the value of the position variable while rotating, the stepper lags at speeds it easy handles without the code for reading the encoder. In addition, the value of position sky-rockets at seemingly random places, an becomes way more negative than positive.

We have tried increasing the baud rate to 115200, inserted some delays here and there. We are thinking it might as well be rooted in the fact that we do not use a closed loop stepper driver, only a normal stepper driver. We also only use two of the outputs of the encoder, not all four( use VCC and ground as well tho).

Stepper Motor (Nema 23)

P Series Nema 23 Closed Loop Stepper Motor 1.2Nm/169.97oz.in ... )

Stepper Driver (Digital Stepper Driver DM542S)

Development board is a Teensy 3.6
Code:

int Pul = 8;  // PUL+ from driver
int Dir = 9;  // DIR+ from driver
int Ena = 10; // ENA+ from driver

int ENCB = 6; //EB+ from encoder
int ENCA = 5;  //EA+ from encoder

int a = 0;
int b = 0;

bool CCW = true;
volatile int pos = 0;

void setup() {
   
  Serial.begin(115200);   //Baudrate of 115200 or 9600
  
  pinMode(Pul, OUTPUT);
  pinMode(Dir, OUTPUT);
  pinMode(Ena, OUTPUT);
  
  pinMode(ENCA,INPUT);
  pinMode(ENCB,INPUT);
  
  attachInterrupt(digitalPinToInterrupt(ENCA),readEncoder,RISING);
        
  digitalWrite(Dir,LOW);// dir LOW - clockwise direction of roation (HIGH - CCW)

  delay(900);       //Delay to let program start before printing values
  
  pos = 0;          //to ensure that pos is zero when start, sometimes it increases before start?
  
  Serial.print("Start. Pos: ");     //To print and track the position per step
  Serial.println(pos);
  Serial.println("----------");
  
  for (int i=0; i <= 400; i++){ //This loop will perform one revolution
    digitalWrite(Ena, LOW);    // optimize values in the for loop to get the highest torque possible
    
    digitalWrite(Pul, HIGH);
    delayMicroseconds(1000);
    
    digitalWrite(Pul, LOW);
    delayMicroseconds(1000); //original value 15000, min value 2000 ca.
    
    Serial.print(i);        //print and track step compared to measured rotation
    Serial.print(" : ");
    Serial.println(pos);
    
  }

  digitalWrite(Dir,HIGH);           // change direction, now rotate CCW
  Serial.print("Pause. Pos: ");
  Serial.println(pos);
  Serial.println("----------");
  
  //delay(1000);
  
  for (int i=0; i <= 400; i++){ // This loop will perform one revolution CCW
    digitalWrite(Pul, HIGH);
    delayMicroseconds(1000);
    
    digitalWrite(Pul, LOW);
    delayMicroseconds(1000);
    
    Serial.print(i);      //print and track step compared to measured rotation
    Serial.print(" : ");
    Serial.println(pos);
    
  }
   
  
}

// Nothing needed in loop, just want to measure simple rotation back and forth for now
void loop() {
}



void readEncoder(){               //Interrupt function that gets called everytime EA+ rises
  b = digitalRead(ENCB);          //If EB+ already has risen, increase pos-value by 1, else decrease
  if(b > 0){
    pos++;
  }
  else{
    pos--;
  }
}

Did you program this, including the delays, based on the specifications of the stepper controller, or did you just guess at the delay? You likely need NO delay to get the stepper to turn.
Paul

Does it stop lagging if you comment out this line?
attachInterrupt(digitalPinToInterrupt(ENCA),readEncoder,RISING);

If it still lags, the problem is in displaying text for each step of the stepper and not in measuring the position.

Similarly, try moving the output out of the loop:

  for (int i=0; i <= 400; i++) { //This loop will perform one revolution
    digitalWrite(Ena, LOW);    // optimize values in the for loop to get the highest torque possible
    
    digitalWrite(Pul, HIGH);
    delayMicroseconds(1000);
    
    digitalWrite(Pul, LOW);
    delayMicroseconds(1000); //original value 15000, min value 2000 ca.
  }  // Move the end of the loop up to here
    Serial.print(i);        //print and track step compared to measured rotation
    Serial.print(" : ");
    Serial.println(pos);
    
  // } <- No longer the end of the loop

If that stops the lagging, again it points to the problem being too much text output.

Thank you for your reply Paul_KD7HB! The delay is a value that has been just tested towards. Increasing it will slow the rotation down, decreasing it will up the speed. By removing the line, i.e. having no delay, it will not rotate at all.

Of course, a delay of any type means the interrupts are also turned off during that time.

Where did you get that idea? If that were true, any 'delay()' over 2 milliseconds would cause millis() to lose time.

Not all interrupts.

What interrupts are "turned off" during "a delay of any type"?

Thanks for your reply johnwasser!

Yes, by commenting out the attachInterrupt, the stepper still lags. Also, by commenting out all the Serial.print in the for-loop, it does not lag.

However, by rotating without any Serial-print except at start, midway(rotated 360 one way), and at the end, the pos-variable is printed differently everytime, but for instance now [0,549,-844]. It is supposed to be [0,1000,0], (start,360,back), of which our result makes no sense.

A remarkable problem is also that it heats up, after a couple of go's it is really hot.

Any idea how to measure real position, working around this problem?

We tried now also to not use delay, rather millis(), kind of the BlinkWithoutDelay()-method. We made it rotate successfully, but we are still not able to measure position. Output is " End. Pos: 836
", and varies between 790 and 840, but is supposed to be 1000, and should be very accurate, at least we want it to be.

Code:

int Pul = 8;  // PUL+ from driver
int Dir = 9;  // DIR+ from driver
int Ena = 10; // ENA+ from driver

int ENCB = 6; //EB+ from encoder
int ENCA = 5;  //EA+ from encoder

int a = 0;
int b = 0;

int interval_tall = 1;
unsigned long previousMillis = 0;
unsigned long currentMillis = 0;

bool CCW = true;
volatile int pos = 0;

void setup() {
  Serial.begin(115200);   //Baudrate of 115200 or 9600
  
  pinMode(Pul, OUTPUT);
  pinMode(Dir, OUTPUT);
  pinMode(Ena, OUTPUT);
  pinMode(ENCA,INPUT);
  pinMode(ENCB,INPUT);


  
  attachInterrupt(digitalPinToInterrupt(ENCA),readEncoder,RISING);
        
  digitalWrite(Dir,LOW);// dir LOW - clockwise direction of roation (HIGH - CCW)

  
  Serial.print("Start. Pos: ");     //To print and track the position per step
  Serial.println(pos);
  Serial.println("----------");

  
  for (int i=0; i <= 400; i++){ //This loop will perform one revolution
    digitalWrite(Pul, HIGH);
    currentMillis = millis();
    while((unsigned long)(currentMillis - previousMillis) < interval_tall){
      currentMillis = millis();
    }
    
    previousMillis = currentMillis;
    digitalWrite(Pul, LOW);
    
    while((unsigned long)(currentMillis - previousMillis) < interval_tall){
      currentMillis = millis();
    }
 
    previousMillis = currentMillis;
    
    //Serial.println(i);        //print and track step compared to measured rotation
    /*
    Serial.print(" : ");
    Serial.println(pos);
    /*
    Serial.print(" : ");
    Serial.print(currentMillis);
    Serial.println(" : ");
    
    //Serial.println(previousMillis);
    */
  }
  
  Serial.print("End. Pos: ");
  Serial.println(pos);
  Serial.println("----------");

}

// Nothing needed in loop, just want to measure simple rotation back and forth for now
void loop() {
}



void readEncoder(){               //Interrupt function that gets called everytime EA+ rises
  b = digitalRead(ENCB);          //If EB+ already has risen, increase pos-vlaue by 1, else decrease
  if(b > 0){
    pos++;
  }
  else{
    pos--;
  }
}

It could well be a stepper motor is not the best solution . If it’s connected to significant mass for its power output , it may well miss steps during operation and certainly when there is kick back from the wheels
A conventional servo system with a DC motor/gearbox and feedback pot/encoder to measure rate/position - that is how electric power steering on a car works .

With a stepper motor the idea is you know where the output position is on the basis of the number of steps you’ve given it , same for rate of position change. So no feedback is needed, but if the load causes missed steps you’ve no chance. ( each step has to rapidly accelerate and move the mass)

A look at what is the “ state of the art “ is worthwhile…

A good read

delay() doesn't change any interrupts - it busy-waits for millisecond interrupts to happen in fact.

That should be INPUT_PULLUP, or better still add some physical pullups to the encoder lines, perhaps 4k7 sort of value, to prevent noise-pickup affecting the count.