Calling AcelStepper from interupt

1,930 views
Skip to first unread message

thingomy

unread,
Apr 1, 2013, 3:49:11 PM4/1/13
to accels...@googlegroups.com
I've been lurking on here for a while, and have searched for references to this, but see nothing that looks relevant. I'm completely new to using the library, but am very impressed so far.

I'd like to write something that basically boils down to a custom g-code interpreter, my needs are not too complex so it will be massively cut down relative to the likes of GRBL etc, but it will be custom enough that I do want to do it myself. Most instructions will be to move to a location, followed by progress report every few hundred ms, there will be no high frequency short moves happening.

I've just started writing pseudo code, and the code effectively checks the serial port then runs stepper.run() time about. Unfortunately things get a little more complex when data is found on the serial port as all of the code to process such events must be filled with frequent calls to stepper.run() to avoid skipping steps. I could use trial and error to determine how far apart in the code such calls can be, but this all feels a little cluttered and inefficient.

What I'd like is to offload the call to stepper.run() to a timed interrupt such that I can ignore it from the body of my code, has any one tried this? Are there any references or examples that are available? I'm confident that it wouldn't be hard to do, I just can't help but think that there must be a gotcha in there somewhere. I'm happy for the interrupt to be taking 75%+ of the CPU time, as the light text processing is neither time consuming or time sensitive.

Even better, it would potentially be possible to look deeper into other workings of the library, and tell the interrupt to fire off at exactly the right time, though I'm not sure how complex this would be to do.

I will be using either 1 or 2 steppers that will be doing heavy physical work, and will be close to their limits in terms of speed and torque, as well as this there will be a need for low vibration and shock, so it's quite important that the timings are reliable and accurate.

Thanks,
JR Peterson

Mike McCauley

unread,
Apr 1, 2013, 9:11:20 PM4/1/13
to accels...@googlegroups.com
Hi,

Ive never tried putting the run() call inside a timer interrupt.

You only have to call run() at least once every min step interval time, so if
you are only stepping slowly, you only have to all run() once at the end of
your main loop.

Cheers.
--
Mike McCauley mi...@open.com.au
Open System Consultants Pty. Ltd
9 Bulbul Place Currumbin Waters QLD 4223 Australia http://www.open.com.au
Phone +61 7 5598-7474 Fax +61 7 5598-7070

Radiator: the most portable, flexible and configurable RADIUS server
anywhere. SQL, proxy, DBM, files, LDAP, NIS+, password, NT, Emerald,
Platypus, Freeside, TACACS+, PAM, external, Active Directory, EAP, TLS,
TTLS, PEAP, TNC, WiMAX, RSA, Vasco, Yubikey, MOTP, HOTP, TOTP,
DIAMETER etc. Full source on Unix, Windows, MacOSX, Solaris, VMS, NetWare etc.

Brian Schmalz

unread,
Apr 1, 2013, 9:27:37 PM4/1/13
to accels...@googlegroups.com
Mike,

Are there any variable access issues? If run() is called from within an interrupt, it can happen in the middle of any statement in any of the other AccelStepper functions (that would be called from the mainline). Could that ever cause a problem? (where run() mucks with a variable in the middle of some other function changing the variable too?)

*Brian
--
You received this message because you are subscribed to the Google Groups "accelstepper" group.
To unsubscribe from this group and stop receiving emails from it, send an email to accelstepper...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


Mike McCauley

unread,
Apr 1, 2013, 9:32:10 PM4/1/13
to accels...@googlegroups.com
Hi Brian,

Thanks for your note.
Yes, the subtext is that calling run from within an interrupt handler probably
wont work (reliably).

Cheers.

ANIL HORASAN

unread,
May 8, 2016, 12:12:15 PM5/8/16
to accelstepper
Sorry for waking up this old post but I am struggling with this accelstepper and interrupts issue like thingomy. Is that a classy way of doing this?

What I do basically is: I use 3 stepper motors, buttons and sensors etc. I move the stepper according to the sensor readings. While steppers are moving (for 1 or 2 seconds), I cannot read any sensor or button changes. Therefore I wanted to move .run() function into an interrupt like:

SIGNAL(TIMER0_COMPA_vect) {
  if (stepper1.distanceToGo() != 0){
    stepper1.run();
  }
}

This works for one stepper but I am not sure if it works for microstepping or for high speeds? What should be the frequency of my timer interrupt? Or should I trigger an interrupt when distanceToGo() != 0 and run the interrupt until distanceToGo() == 0 ? I think this will again block my other codes too...

What are your opinions? What is the best way of non-blocking stepper driving?

Mike McCauley

unread,
May 8, 2016, 4:46:15 PM5/8/16
to accels...@googlegroups.com
Hi,

this is not something I have tested or even recommend. It may be that the code
is not interrupt-safe for this.

I would think the frequency to call .run() would be a tradeoff between the
jitter in step timing you can tolerate versus available CPU cycles.

My guess on interrupt frequency would be at least twice as fast as the fastest
stepping speed required,

If it works for one stepper (and I dont say it will) it should work for
several and for microstepping, but of course those takes even more CPU cycles.

Let us know how you go.
Cheers.
--
Mike McCauley VK4AMM mi...@airspayce.com
Airspayce Pty Ltd 9 Bulbul Place Currumbin Waters QLD 4223 Australia
http://www.airspayce.com
Phone +61 7 5598-7474

James Conway

unread,
May 8, 2016, 5:27:35 PM5/8/16
to accels...@googlegroups.com
I use this library on an interrupt handler, arduino Uno, no problems. I understand that its not guaranteed and there may be subtleties that more complex code will expose, but I think that if the interrupt code is kept as brief as possible, the chances for mischief are small. And its so much easier to handle the stepper motors separately from other polling-style events.

The obvious point is to call the run() function more often than needed, by putting it into interrupt handler and making that fires more often, so that you don't miss pulses (as Mike covered). If nothing needs to be done at a particular call, the interrupt handler will return sooner. The interrupt handler I need has just this:
myStepper.run();
I guess with more stepper motors there would be more lines similar to that one.

The setup decides the frequency of interrupts. I pulled info from here:
http://www.engblaze.com/microcontroller-tutorial-avr-and-arduino-timer-interrupts/
From that I adapted code that I will be happy to share, if anyone is interested.

James Conway
> --
> You received this message because you are subscribed to the Google Groups "accelstepper" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to accelstepper...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

ANIL HORASAN

unread,
May 9, 2016, 10:37:26 AM5/9/16
to accelstepper
Thank you both, I would appreciate if you can share your code James :) 

Another quick question then, how can I calculate the stepping frequency? If I have a 1.8 deg stepper, and set my maxSpeed to 200, it means that 200 pulse per second, right? Then 1 ms timer would be enough? 

I did not have a chance to work on it more. When I do, I will post my results. 

Have a good week, everyone :) 


On Monday, April 1, 2013 at 10:49:11 PM UTC+3, thingomy wrote:

James Conway

unread,
May 9, 2016, 2:18:26 PM5/9/16
to accels...@googlegroups.com
Anil,

> Thank you both, I would appreciate if you can share your code James :)

The project using this code runs on an Arduino Uno and attempts to maintain the height of a platform on which metal slide holders are dropped. The slides are ~1.5mm thick, so after each one is dropped the platform is lowered. Two light-sensitive resistors get light on them from LEDs - the top path should be clear and the bottom blocked. If both are blocked, the platform needs to be lowered, if both are clear the platform needs to be raised. Originally I had a robot arm dropping the slide holders (6x9cm) into a box, but they would fall at an angle and jam so that the robot arm would eventually try to push into the heap of metal slide holders sticking out of the box - not good. Now the new slide is dropped onto the tidy pile of slides with no gap to allow it to flip. There is also a switch to raise the platform to the top so that the slides can be removed.

As far as the interrupt code goes, the Auduino setup() routine includes this:

#include <AccelStepper.h>
#include <avr/io.h>
#include <avr/interrupt.h>
...
// initialize the stepper library for 4-wire on the nominated pins
AccelStepper myStepper(AccelStepper::FULL4WIRE, stepperPin1, stepperPin2, stepperPin3, stepperPin4);

/*****************************************************************************
** setup
** =====
*/
void setup()
{
...
// Set up interrupt routine to operate every 0.1ms = 100us
if(!SetUpInterrupts(100))
while(1) { // forever, something has gone wrong
DoBlink(motorUpLedPin, 100);
DoBlink(motorDownLedPin, 100);
}
}
/****************************************************************************/

The code to set up the interrupt service hander is:

/*****************************************************************************
** SetUpInterrupts
** ===============
Set up interrupt routine to service stepper motor run() function.
*/
bool SetUpInterrupts(const int usecs)
{
// initialize Timer1
cli(); // disable global interrupts
TCCR1A = 0; // set entire TCCR1A register to 0
TCCR1B = 0; // same for TCCR1B

// set compare match register to desired timer count (1ms):
// ATmega328 with a 16MHz clock, clk/8
// (# timer counts + 1) = (target time) / (timer resolution)
// = .0001s / 6.25e-8 s * 8
// = 200
const float targetSecs = ((float) usecs) / 1e6;
const float timerSteps = 6.25e-8; // 1/16MHz
int count = 0;
int prescale = 1; // valid values: 1, 8, 64, 256, 1024
do {
count = targetSecs / (timerSteps * prescale);
if(count < 65535) // Timer 1 is 16-bits wide
break;
prescale *= 8;
} while (prescale <= 1024);
if(prescale > 1024) // time too long
return false;
if(prescale == 1 && count < 100) // time too short
return false;

OCR1A = count; // Eg, 200 = 0.1ms - I found 1ms gives rough acceleration
// turn on CTC mode (Clear Timer on Compare Match):
TCCR1B |= (1 << WGM12);
// Set CS10 and CS12 bits for 1024 prescaler:
// CS12 CS11 CS10
// 0 0 0 no clock source, Timer/counter stopped
// 0 0 1 clk/1 no prescaling
// 0 1 0 clk/8
// 0 1 1 clk/64
// 1 0 0 clk/256
// 1 0 1 clk/1024
// 1 1 0 external clock on T1 pin, falling edge
// 1 1 1 external clock on T1 pin, rising edge
switch(prescale) {
case 1:
TCCR1B |= (1 << CS10); // 0 0 1
break;
case 8:
TCCR1B |= (1 << CS11); // 0 1 0
break;
case 64:
TCCR1B |= (1 << CS11) & (1 << CS10); // 0 1 1
break;
case 256:
TCCR1B |= (1 << CS12); // 1 0 0
break;
case 1024:
TCCR1B |= (1 << CS12) & (1 << CS10); // 1 0 1
break;
}
// enable timer compare interrupt:
TIMSK1 |= (1 << OCIE1A);
// enable global interrupts:
sei();

return true;
}
/****************************************************************************/


The interrupt service hander routine itself is just this:

/*****************************************************************************
** ISR
** ===
Interrupt service routine for when Timer 1 matches compare value
*/
ISR(TIMER1_COMPA_vect)
{
myStepper.run();
}
/****************************************************************************/


The Arduino loop() function includes calls to modify stepper motor activity, eg:

myStepper.setAcceleration(accel); // steps/sec/sec
myStepper.stop(); // decelerate to a stop at new position
myStepper.setMaxSpeed(motorSpeed); // steps/sec
myStepper.move(-1000); // start moving the distance specified

For example, a blocking stop loop (ie, stop before doing anything else) can be programmed as follows:

const int kMotorDecelNormal = 500;
...
if(state != 0) { // not stopped already
myStepper.setAcceleration(kMotorDecelNormal); // steps/sec/sec
myStepper.stop(); // decelerate to a stop at new position
while(myStepper.distanceToGo() != 0) ; // wait for stop
}

Even while stuck in the "wait for stop" while loop, the myStepper.run(); function continues to be called in the interrupt handler.

I hope that is enough to get you going.

James Conway

BitWorksInc BitWorksInc

unread,
Jan 19, 2021, 4:02:08 PM1/19/21
to accelstepper
I used portions of the code , and the int routine as is on a wavgat nano, set the int rate to 20us (much lower caused problems). Appears to work.

Having not read the depths of the accelstepper  code, does the .run() "test and Step" if required meaning there will be a jitter in the step time varying with the interval of being called. Or does .run() set up the action for a "burried" timer interrupt that does the actual stepping.

There is a very real danger of position etc, which are modified during run() being read back during a multibyte operation in the app level. For this reason it would be better to have a flag to request the int() to copy position to an applevel variable and clear the flag to let the app know this was done. 

Additionally I think I will make run bypass flag to tell the int() to  just return if  it should be turned off.

Does anyone know if  once the timer counter rate on a wavgat processor (nano) have the same count rate once the patches are made to the arduino IDE.

Mike McCauley

unread,
Jan 19, 2021, 6:22:15 PM1/19/21
to accelstepper, BitWorksInc BitWorksInc
Hello,

On Wednesday, 20 January 2021 07:02:08 AEST BitWorksInc BitWorksInc wrote:
> I used portions of the code , and the int routine as is on a wavgat nano,
> set the int rate to 20us (much lower caused problems). Appears to work.

Well done.

>
> Having not read the depths of the accelstepper code, does the .run() "test
> and Step" if required meaning there will be a jitter in the step time
> varying with the interval of being called.

Yes: if you do not call it regularly there wil be jitter.

> Or does .run() set up the action
> for a "burried" timer interrupt that does the actual stepping.

No the stepping is done (if necessary) inside the run() and is not deferred to
any later time.


>
> There is a very real danger of position etc, which are modified during
> run() being read back during a multibyte operation in the app level. For
> this reason it would be better to have a flag to request the int() to copy
> position to an applevel variable and clear the flag to let the app know
> this was done.
>
> Additionally I think I will make run bypass flag to tell the int() to just
> return if it should be turned off.
>
> Does anyone know if once the timer counter rate on a wavgat processor
> (nano) have the same count rate once the patches are made to the arduino
> IDE.

No info from me.

Cheers.
--
Mike McCauley VK4AMM mi...@airspayce.com
Airspayce Pty Ltd 9 Bulbul Place Currumbin Waters QLD 4223 Australia
http://www.airspayce.com 5R3MRFM2+X6
Phone +61 7 5598-7474



Reply all
Reply to author
Forward
0 new messages