Solved: unintended pause with a stepper motor during the ramp up phase

Go To Last Post
12 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Everyone,

 

I have a program for smooth ramping up, max speed, and then ramping down of a stepper motor.  The problem I run into is a pause during the ramping up phase caused by a while loop at the end of my program.  I am not sure what causes this pause in the stepper motor.  I have ensured that it is not a logic problem, but I suspect it is an error with the ISR.  Can someone please give me a suggestion as to what is going on with this program pasted below?  I am using the Arduino Uno board with the Atmega 328p, and a Gecko driver.

 

/*Begining of Auto generated code by Atmel studio */
#include <Arduino.h>

/*End of auto generated code by Atmel studio */

//Beginning of Auto generated function prototypes by Atmel Studio
void moveNSteps(int steps);
void moveToPosition(int p, bool wait);
ISR(TIMER1_COMPA_vect );
//End of Auto generated function prototypes by Atmel Studio

#define DIR_PIN          5
#define STEP_PIN         2
//#define ENABLE_PIN       4

#define STEP_HIGH        PORTD |=  0b00000100;
#define STEP_LOW         PORTD &= ~0b00000100;

#define TIMER1_INTERRUPTS_ON    TIMSK1 |=  (1 << OCIE1A);
#define TIMER1_INTERRUPTS_OFF   TIMSK1 &= ~(1 << OCIE1A);

unsigned long c0;

void setup() {
  Serial.begin(9600);

  pinMode(STEP_PIN,   OUTPUT);
  pinMode(DIR_PIN,    OUTPUT);
  //pinMode(ENABLE_PIN, OUTPUT);

  noInterrupts();
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;
  OCR1A = 125;
  TCCR1B |= (1 << WGM12);
  //select prescaler: 16MHz/64/50000=5Hz overflow or 16MHz/64/25000
  TCCR1B |= ((1 << CS11) | (1 << CS10));
  interrupts();

  c0 = 800; //2000 * sqrt( 1 ) * 0.67703;
}

volatile int dir = 0;
unsigned int maxSpeed = 10;
volatile unsigned long n = 0;
volatile long newvar = 0;
volatile float d;
volatile unsigned long stepCount = 0;
volatile unsigned long rampUpStepCount = 0;
volatile unsigned long totalSteps = 0;
volatile int stepPosition = 0;
volatile bool movementDone = false;
int count = 0;

void moveNSteps(int steps) {

  digitalWrite(DIR_PIN, steps < 0 ? HIGH : LOW);
  dir = steps > 0 ? 1 : -1;
  totalSteps = abs(steps);
  d = c0;
  //OCR1A = d;
 // stepCount = 0;
  //n = 0;
  //rampUpStepCount = 0;
  //movementDone = false;

  TIMER1_INTERRUPTS_ON
}

void moveToPosition(int p, bool wait = true) {
  moveNSteps(p - stepPosition);
  while ( wait && ! movementDone );
}

ISR(TIMER1_COMPA_vect)
{
  if ( stepCount < totalSteps ) {

    //STEP_HIGH

     //STEP_LOW

     digitalWrite(STEP_PIN, digitalRead(STEP_PIN) ^ 1);

    stepCount++;

    stepPosition += dir;

  }
  else {
    movementDone = true;
     noInterrupts();
    TIMER1_INTERRUPTS_OFF
  }

  if ( rampUpStepCount == 0 ) { // ramp up phase
    n++;
    d = d - ((2 * d) / ((4 * n) + 1)); 

    newvar = n + 1;
    if ( d <= maxSpeed ) {

      d = maxSpeed;

      rampUpStepCount = stepCount;

    }
    if ( stepCount >= totalSteps / 2 ) { 

      rampUpStepCount = stepCount;
    }
  }
 if ( stepCount >= totalSteps - rampUpStepCount ) { // ramp down phase

    newvar--;
    n--;
    d = (d * ((4 * newvar) + 1)) / (4 * newvar + 1 - 2);
  }

  OCR1A = d;
}

void loop() {

  //Serial.println("Starting...");

 moveToPosition( 12000, false );

while ( !movementDone || count <3){
  Serial.println("I am here");
  count++;
  delay(100);

};

movementDone = true;
     noInterrupts();
     TIMER1_INTERRUPTS_OFF

}

 

Reformatted code - JGM

Last Edited: Tue. Jan 23, 2018 - 12:18 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Make sure the stepper motor ISR is higher priority than your serial port ISR.  It's likely the pause is due to servicing the Serial.println() call inside the loop (while the stepper is moving)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It's probably not the issue but I'd start by ditching 'DigitalWrite' as, IIRC, it's known to be a cycle hog.

"This forum helps those that help themselves."

"How have you proved that your chip is running at xxMHz?" - Me

"If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand." - Heater's ex-boss

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Brian Fairchild wrote:
I'd start by ditching 'DigitalWrite' as, IIRC, it's known to be a cycle hog.

 

DigitalWrite() is not just a cycle hog. Its a real monster.

Absolutely unfit for anything but slow stuff such as turning a relay on / off every now and then.

And on top of that, you are calling it from within an tinterrupt.

 

GCC does not like function calls from within interrupts.

It gets easily confused a bit with the result that it ( sometimes / often ?) pushes all registers to the stack when entering the interrupt routine if it has function calls in it.

And the AVR architecture has a lot of registers to push... (And they also have to be popped back).

 

To determine if performance is an issue you can try running the code at 12/ to 1/8 the speed it normally does.

Does it run smoothly if you do that?

 

This also looks suspicious:

tiptonspiderj1 wrote:
digitalWrite(STEP_PIN, digitalRead(STEP_PIN) ^ 1);

To invert a pin you should XOR it with itself.

 

To streamline your code it is best to move calculations out of the ISR.

You know that you want to accelerate, run at constant speed and decellerate.

You can calculate the stepCounts in advance outside your interrupt and then just compare with these pre calculated values.

 

Make a state machine in your ISR with a switch() statement instead of all those if() statements.

It's easier to read and probably also a bit faster to execute.

 

I also do not like the multiple if() statements in your interrupt.

These are also bad for timing. It seems that the code below is executed on each interrupt:

tiptonspiderj1 wrote:
if ( stepCount >= totalSteps - rampUpStepCount ) { // ramp down phase

 

Another small thing (which mostly makes your code a bit easier to understand for humans) is to completely remove the "movementDone" var.

Use stepCount as a down counter.

Set it to a number if you want to move.

Decrement it in the ISR.

When it reaches 0 the movement is finished.

 

You also seem to have far to many variables. It confuses me a bit.

 

Where does your stepper speed algorithm come from?

David Austin has written a very nice algorithm for lineair acceleration / decelleration which only uses a single division for each step.

 

Also: defining macro's such as STEP_HIGH without using it and commented out code is very bad practice for posting examples.

It seems that Jim reformatted your code, but it still has very many empty lines and code is not lined out properly.

Stuff like that is very demotivating for others to spend any time on reading your code.

Show us you put some effort in it and we're likely to help you better.

 

Oh, and ignore ScotMN #2.

m328 does not have different interrupt levels. No different priorities.

 

A simple thing you can try though is to completely disable the uart interrupts.

Initiate your move by pressing a button or something.

This can help narrowing down the cause of your problem.

 

Paul van der Hoeven.
Bunch of old projects with AVR's:
http://www.hoevendesign.com

Last Edited: Sat. Jan 20, 2018 - 12:05 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I appreciate the positive feedback and I will try the suggestions to see if they fix the problem or at least help narrow it down.  I will also post updated code that is more user friendly on Monday.  Thanks for the help/suggestions.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Why are you using float?  That seems a  bit ridiculous.  The available speeds are integer settings.

 

volatile float d;

 

 

You should be able to write a simple ramp up/down demo without too many code lines, including the code that actually steps the motor.

When in the dark remember-the future looks brighter than ever.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avrcandies wrote:
Why are you using float? That seems a bit ridiculous.

Oops, I missed that. Upvoted the candy boy.

Look at the David Austin article.

 

https://duckduckgo.com/html?q=da...

https://www.embedded.com/design/...

 

As said before Austin does all stepper delay calculations in integers.

Atmel has also written an AN for this, but the original article is much better written.

With a small mod you can calculate the timer offsets once and put them in an array. This can boost your stepper rate to >100kHz.

And lot's of libraries use that algorithm.

He should be almost as famous as Bresenham.

 

Another tip:

Never ever use single letter variables.

If your variables have decent names you can (double) click on them and highlight them with a search.

Refactoring maintenance (search & replace) also works a lot better.

 

 

Paul van der Hoeven.
Bunch of old projects with AVR's:
http://www.hoevendesign.com

Last Edited: Sat. Jan 20, 2018 - 12:04 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

IMO decently named variables primarily helps the human brain. An IDE can search/highlight variables, their definition and usages regardless of how crappy the name is. Same for refactoring.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I would be interested in more links/references on the comment about DigitalWrite. Not to say I'd be surprised by that, I've been horrified by all the Arduino code I've read.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Oh, we've has the discussion here, probably several times.

 

If my memory serves me it's like this: Because the Arduino pin number get translated to a AVR port+pin-number at runtime, every time you do digital I/O. A single write to a digital I/O pin takes something like 25 to 50 clocks ( to be compared with two clocks for the "corresponding" SBI/CBI or port |= 1<<pinNumber / port &= ~(1<<pinNumber) )

 

For the details, just have a look in the Arduino sources.

 

You can of-course set up an Arduino project that does some simple digital I/O, run it in Studio using the Simulator and keep an eye on the cycle counter / stopwatch.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avrcandies wrote:

Why are you using float?  That seems a  bit ridiculous.  The available speeds are integer settings.

 

volatile float d;

 

 

You should be able to write a simple ramp up/down demo without too many code lines, including the code that actually steps the motor.

Then where d is assigned to OCR1A, may result in a sick puppy. Writing to timer registers requires atomic operations in order.

It all starts with a mental vision.

Last Edited: Sun. Jan 21, 2018 - 12:25 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

I appreciate the feedback and will continue to incorporate your suggestions that make since to me like not using the float variable type.  I solved my problem with some of your suggestions.  Here is my code that works...still fixing a few small details.  What's in bold is what I changed to get the program working smoothly without a pause.

 

//set up the direction & step pins for the arduino uno board
#define DIR_PIN          5
#define STEP_PIN         2

//assign which port is used for stepping high/low
#define STEP_HIGH        PORTD |=  0b00000100;
#define STEP_LOW         PORTD &= ~0b00000100;

 

#define TIMER1_INTERRUPTS_ON    TIMSK1 |=  (1 << OCIE1A);
#define TIMER1_INTERRUPTS_OFF   TIMSK1 &= ~(1 << OCIE1A);

 

//setup variables in the program
unsigned long c0;
volatile int dir = 0;
unsigned int maxSpeed = 10;
volatile unsigned long n = 0;
volatile long newvar = 0;
volatile float d;
volatile unsigned long stepCount = 0;
volatile unsigned long rampUpStepCount = 0;
volatile unsigned long totalSteps = 0;
volatile int stepPosition = 0;
volatile bool movementDone = false;
int count = 0;

 

void setup() {
 
  //set baud rate for serial communication
  Serial.begin(9600);
  //setup pins for output
  pinMode(STEP_PIN,   OUTPUT);
  pinMode(DIR_PIN,    OUTPUT);
  // disable all interrupts
  noInterrupts();
  // initialize timer1 16bit timer
  TCCR1A = 0;
  TCCR1B = 0;
  // initialize counter for timer
  TCNT1  = 0;
  OCR1A = 125;                            
  TCCR1B |= (1 << WGM12);
  //select prescaler for timer
  TCCR1B |= ((1 << CS11) | (1 << CS10));
  //enable all interrupts
  interrupts();
  //initialize variable for starting delay in rampup acceleration routine
  c0 = 1600;
 
}

 

//This function tells the motor how many steps to move
void moveNSteps(int steps) {
 
  digitalWrite(DIR_PIN, steps < 0 ? HIGH : LOW); 
  dir = steps > 0 ? 1 : -1; 
  totalSteps = abs(steps);
  d = c0; 
  OCR1A = d; 
  movementDone = false; 
  TIMER1_INTERRUPTS_ON
 
}

 

//This function tells the motor what position to move to
void moveToPosition(int p, bool wait = true) {
 
  moveNSteps(p - stepPosition);
    
}

 

ISR(TIMER1_COMPA_vect){
 
  if ( stepCount < totalSteps ) {

 

    //This made the stepper motor function much faster than using digital write and digital read.
    STEP_HIGH
    delayMicroseconds(10);
    STEP_LOW
    delayMicroseconds(10);

    stepCount++;
    stepPosition += dir;
  } 
  else {   
    movementDone = true;
    noInterrupts();
    TIMER1_INTERRUPTS_OFF
  }
  // ramp up phase
  if ( rampUpStepCount == 0 ) {
    n++;
    d = d - ((2 * d) / ((4 * n) + 1)); 
    newvar = n + 1;
    if ( d <= maxSpeed ) {
      d = maxSpeed;
      rampUpStepCount = stepCount;
    }
    if ( stepCount >= totalSteps / 2 ) { 
      rampUpStepCount = stepCount;         
    }
  } 
  if ( stepCount >= totalSteps - rampUpStepCount ) { // ramp down phase
    newvar--;
    n--;
    d = (d * ((4 * newvar) + 1)) / (4 * newvar + 1 - 2);
  }
  OCR1A = d;
 
}

 

void loop() {

  moveToPosition( 12000, false );

  while (!movementDone){ //This was the main culprit  of my problem with the pause.
    count++;
    delay(100); 
   };
 
  movementDone = true;
  noInterrupts();
  TIMER1_INTERRUPTS_OFF
 
}