Using a stepper motor with closed loop feedback

I am using a stepper motor and a quadrature encoder for my project. I am using a LS7366 encoder chip to interface the encoder to my Nano using SPI comm. To command the stepper I am using a for -next loop setup. I can use the break command to stop positioning, but to read the encoder at a realtime positioning rate I will need to use different logic. I am wondering how I could use a goto command to interupt the loop commanding the stepper motor. I am not sure about this........ can someone suggest the best method to read the encoder and at the same time command the stepper motor.

Help me understand the problem you are trying to solve. The stepper motor can ONLY move in the discrete number of steps you tell it to make. You can keep track of the steps.

To use an encoder with the stepper, it will need to have the number of segments that match the number of steps your stepper will make in one revolution. And it will have to be synced with the stepper.

Paul

I am using an A4988 stepper driver.
My design uses a NEMA 17 stepper motor that has an attached gearbox (ratio is 5.18:1), which rotates a belt pulley that is coupled by belt to another belt pulley driving a shaft. This shaft (23-inches long) has a cog belt pulley at each end. Each of the cog belts are attached to a sled that moves 46 inches. each of the cog belts run around a second shaft with cog belt pulleys at the far end (46 inches) rotating the shaft which drives the encoder. The end of the cog belt is attached to the other side of the sled. :confused: The sled is 23 inches wide, 2 belts are needed to prevent crabbing. (I could do this with a DC motor using an H-bridge but the stepper has more initial power than the DC motor at slow speeds.) With all of the different gearing I need to know exactly how far the sled has traveled. The sled will move 2 other axis Y and Z.

I am used to using calls in basic, to jump out of routines, do something and then return. Arduino code doesn't seem to work well that way. I need to be educated about Arduino coding if there is a way to do this simply.

unLike:
I am used to using calls in basic, to jump out of routines, do something and then return. Arduino code doesn't seem to work well that way. I need to be educated about Arduino coding if there is a way to do this simply.

They're called functions in C++.

You don't want a goto. In C++ code you should never need goto. We have better control structures here than in Basic so anything you were going to do with goto can be done better without.

In an arduino sketch you have the loop function doing the looping. You don't need to use a loop within that loop to handle your steps. Let the loop function do the looping and on each pass of loop read your encoder and take a step.

Hi,
Welcome to the forum.

Please read the first post in any forum entitled how to use this forum.
http://forum.arduino.cc/index.php/topic,148850.0.html then look down to item #7 about how to post your code.

Please post the code you have using code tags.

Thanks.. Tom.. :slight_smile:

Hi,

unLike:
I am using an A4988 stepper driver.
My design uses a NEMA 17 stepper motor that has an attached gearbox (ratio is 5.18:1),
Which rotates a belt pulley that is coupled by belt to another belt pulley driving a shaft.
This shaft (23-inches long) has a cog belt pulley at each end.
Each of the cog belts are attached to a sled that moves 46 inches.
Each of the cog belts run around a second shaft with cog belt pulleys at the far end (46 inches) rotating the shaft which drives the encoder.
The end of the cog belt is attached to the other side of the sled.
:confused:
The sled is 23 inches wide, 2 belts are needed to prevent crabbing.
(I could do this with a DC motor using an H-bridge but the stepper has more initial power than the DC motor at slow speeds.)
With all of the different gearing I need to know exactly how far the sled has traveled.
The sled will move 2 other axis Y and Z.

You will also need a limit switch to signal when the sled is in the HOME position so you have a reference point.
What do you want to accomplish, stopping the sled at predetermined positions for some process, or just back and forth motion?
Tom... :slight_smile:

As always, read and understand the "several things at once" and "blink withour delay" demos.

Many stepper motor libraries are non-blocking. You call a method on an object to move the stepper to a position, and then each time you loop you call a "give me a time slice" method on the object. The object has a "yes, I have finished moving" method that you can query. It's a different way of programming to a "do this, then do that" model. You need to think about your sketch in terms of it being a state machine.

@unLike, you have not told us how many encoder steps will occur in a second. That will affect the way you deal with them. If the steps are slow then they can probably be detected using digitalRead().

But if the steps are fast then probably the best way is to use the encoder to trigger an interrupt and your Interrupt Service Routine (ISR) can count the number of encoder steps.

I suspect a much simpler approach would be to figure out, with some simple experiments, how many stepper steps correspond to (say) a 1 centimeter movement of the sled. Then there would be no need for an encoder.

...R
Several Things at a Time

Stepper Motor Basics
Simple Stepper Code

unLike:
To command the stepper I am using a for -next loop setup.

There’s your problem. No, I don’t mean the “next”. A for() loop is the wrong construct for this.

unLike:
can someone suggest the best method to read the encoder and at the same time command the stepper motor.

Try to re-write your program to look something like this…

void loop() {
  readEncoder();
  if(stepperIsNotFinished()) {
    stepperOneStep();
  }
  readCommandButtons(); //you didn't say how this is controlled. I'm guessing you're using buttons.
  outputToScreen();    //you didn't say if you have any screen or user feedback. Whatever you have should go into this function
}

I don’t understand why you need a dedicated SPI chip to process the decoder. With the gear ratios you’ve given, the encoder probably isn’t producing a thousand pulses per second. The Arduino encoder library written by Paul Stoffregen is extremely good.

MorganS, Tom, Robin2,
The LS7366 encoder interface uses SPI to communicate encoder counts to the Nano. The LS7366 accumulates encoder counts realtime. The encoder chip, is setup to count all four edges of the quadrature pulses. One revolution of the encoder produces 2400 counts, this chip is necessary due to the speed of the arriving pulses. This chip can count up to 32 bits, well over 4 million counts, and is an up/down counter. Last night I think I have found a way to interupt the for loop to achieve what I wanted. thanks for your replies, they made me think of other ways to approach this problem.
I have posted the code that I will be using.

void loop() {
 mcp0= mcp.digitalRead(0);//Reference PB
 mcp1= mcp.digitalRead(1);//Jog FWD PB
 mcp2= mcp.digitalRead(2);//Jog REV PB
 mcp3= mcp.digitalRead(3);//End of Travel Soft Ext
 mcp4= mcp.digitalRead(4);//End of Travel Soft Ret
 mcp5= mcp.digitalRead(5);//Start Motion PB
 mcp6= mcp.digitalRead(6);//Stop Motion PB
 mcp7= mcp.digitalRead(7);// Reference  magnetic SW 
 if(mcp0 == 0) {initEncoders();clearEncoderCount();mcp.digitalWrite(15, LOW);} else {mcp.digitalWrite(15, HIGH);}
 if(mcp1 == 0){mcp.digitalWrite(14, LOW);} else {mcp.digitalWrite(14, HIGH);}
 if(mcp2 == 0){mcp.digitalWrite(13, LOW);} else {mcp.digitalWrite(13, HIGH);}
 if(mcp3 == 0){mcp.digitalWrite(12, LOW);} else {mcp.digitalWrite(12, HIGH);}
 if(mcp4 == 0){mcp.digitalWrite(11, LOW);} else {mcp.digitalWrite(11, HIGH);}
 if(mcp5 == 0){Start = 2; Run = 20360;X = 0;R = 0;}// start positioning
 if(mcp6 == 0){Start = 3; mcp.digitalWrite(9, LOW);digitalWrite(EN, HIGH);} else {mcp.digitalWrite(9, HIGH);}
 if(mcp7 == 0){mcp.digitalWrite(8, LOW);} else {mcp.digitalWrite(8, HIGH);}
 // test the Stepper motor
 if(mcp1 == 0 && Start == 3 || mcp2 == 0 && Start == 3) {Start = 2;}
 if(Start==2) { 
  digitalWrite(EN, LOW);digitalWrite(DIR, LOW);mcp.digitalWrite(10, LOW);
  for(X = 0; X < Run; ++X){
  if (mcp.digitalRead(6)==LOW ||mcp.digitalRead(3)==LOW ||mcp.digitalRead(4)==LOW){ R = X; break;}
  if (X > R+1036){R = X;readEncoder();SelectStep();}
  digitalWrite(step_pin, HIGH);
  delayMicroseconds(200);
  digitalWrite(step_pin, LOW);
  delayMicroseconds(200);
   } 
  
  }
  if(X >= Run) {digitalWrite(EN, HIGH);Start = 0; mcp.digitalWrite(10, HIGH);} 
  
 
 if(PV < 200) {SelectStep();} else {digitalWrite(MS1,LOW);digitalWrite(MS2,LOW);digitalWrite(MS3,LOW);}
 readEncoder();
 lcd.setCursor (0,1);lcd.print(String(CV)+"   " + String(PV)+"   ");
 if( PV > 10) {lcd.setBacklight(HIGH);} else {lcd.setBacklight(LOW);}
 
  
}
void SelectStep(){
  // sPi = Step per inch
if(PV >  150) {digitalWrite(MS1,LOW);digitalWrite(MS2,LOW);digitalWrite(MS3,LOW);}   
if(PV <  150) {digitalWrite(MS1,HIGH);Run = 2*X;} 
if(PV <  100) {digitalWrite(MS2,HIGH);digitalWrite(MS1,LOW);Run = 4*X;} 
if(PV <  50) {digitalWrite(MS2,HIGH);digitalWrite(MS1,HIGH);Run = 8*X;} 
}





void readEncoder() {
  
  // Initialize temporary variables for SPI read
    int count_1, count_2, count_3, count_4; 
  
  // Read encoder 
    digitalWrite(slaveSelectEnc1,LOW);      // Begin SPI conversation
    SPI.transfer(0x60);  //was 60                   // Request count
    count_1 = SPI.transfer(0x00);           // Read highest order byte
    count_2 = SPI.transfer(0x00);           
    count_3 = SPI.transfer(0x00);           
    count_4 = SPI.transfer(0x00);           // Read lowest order byte
    digitalWrite(slaveSelectEnc1,HIGH);     // Terminate SPI conversation 
    
  // Calculate encoder count
  CV = (count_1 << 8) + count_2;
  CV = (CV << 8) + count_3;
  CV = (CV << 8) + count_4;
  
  //if(Start == 2) { 
  if(CV > CP){PV = (CV-CP);}
  if(CP > CV){PV = (CP-CV);}
  lcd.setCursor (0,1);lcd.print(String(CV)+"   " + String(PV)+"   ");
  //if(PV >= 2500) {Run = 200; }
  //if(PV < 2500) { Run = int(PV/13);}
  
//}
  
}

Lesson #1 on Use of Stepper Motors:

If you design the drive properly, there is not a reason in the world to use an encoder with a stepper motor. The ONLY way a stepper motor will NOT be where you commanded, is due to a hardware failure, or you have asked it to provide more torque than it is capable of providing. Adding an encoder will NOT help in either case. Stepper motors ALWAYS operate at maximum torque. Size the motor properly for the dynamic load, drive the motor properly (driving with proper voltage and current, and drive with achievable acceleration rates), and the motor will ALWAYS go where it is told to go. NO encoders needed. The ONLY thing an encoder can do it alert you that you've screwed up in driving the motor. It CANNOT help you "catch up" if you ask it to move faster than it is capable of.

Regards,
Ray L.

If you write everything on one line it’s hard to read and hard to modify. This appears to be the for() loop at the core of your program, after the auto-format has been applied:

   for (X = 0; X < Run; ++X) {

if (mcp.digitalRead(6) == LOW || mcp.digitalRead(3) == LOW || mcp.digitalRead(4) == LOW) {
       R = X;
       break;
     }
     if (X > R + 1036) {
       R = X;
       readEncoder();
       SelectStep();
     }
     digitalWrite(step_pin, HIGH);
     delayMicroseconds(200);
     digitalWrite(step_pin, LOW);
     delayMicroseconds(200);
   }

This appears to be reading some of the switches: 6, 3 and 4. You can give these names, just like you gave the name step_pin to an Arduino pin. But earlier, you read these switches and assigned them to global variables. Now, when the emergency-stop button is pressed, you detect that but the earlier variable mcp6 isn’t updated. So now how does any code following this know what the state of that switch is?

[mcp6 is a poor name for a variable. Call it StopPBPressed or something meaningful to you. Any time you’re using numbers in variable names, stop and think if this is the correct solution.]

This might work for you but it’s going to be a nightmare to debug and maintain in the future. Try to use more functions. A readAllSwitches() function would be useful because you’re reading many of the switches in many places. Even though most of those places don’t need to read the start button (for example) the time delay of the “unnecessary” digitalRead() will be close enough to zero that it doesn’t matter.

RayLivingston:
If you design the drive properly, there is not a reason in the world to use an encoder with a stepper motor.

+1

It's what I was trying to suggest in Reply #7

...R

Follow up:
I have found an even better way to control the stepper motor using an A4988 stepper controller. By using a 555 timer IC as an oscillator to toggle the A4988 Step input. I am using a Nano Output to power/enable the 555 oscillator. I am now not hampered by the For Loop in reading the encoder that communicates on SPI, and can position to with in 2 thousands of an inch using a dial mic. Using this method of still allows the use of micro stepping as well. :slight_smile:

Unless you're regular single coil at a time stepping, you're going to loose steps on lower price motors...period. Go buy a 28byj and see for yourself.