Using SPI within an ISR?

Hi all,

I'm currently working on a problem, and trying to determine if SPI will meet the needs I have for a particular implementation.

Here's the gist:

I'm using all of Timer0 (millis, delay, etc.), Timer1 ("async" pulsing), and Timer2 (async timed secondary activity that may or may not occur while the activity from Timer1 is happening).

FWIW, one of the things I'm doing is driving four step/dir stepper motor drivers. And using Timer1 to do async pin changes for the step pins on the right interval to achieve the given speed and distance travel (including velocity curves, etc.)

Now, to free up some pins, I've considered using an SPI interface to the four drivers, going from 9 to 4 pins. A real win, I'd say!

BUT, I'm at the point of designing hardware, and before I spend a ton of time/money designing hardware to this end, and banging my head against code that just could never work, I'm hoping for a little knowledge from the more experienced types.

Namely:

Can I, in SPI master mode, call SPI transmit functions inside an ISR, presuming interrupts are disabled at this time?

Namely, sections 18.3 and 18.5 of the datasheet keep saying "if SPI interrupt [mode] is enabled" -- does this mean I can use SPI without using interrupts?

Thanks in advance!

!c

I'm better than 96% sure it is okay in general.

I can't find the reference to

Namely, sections 18.3 and 18.5 of the datasheet keep saying "if SPI interrupt [mode] is enabled" -- does this mean I can use SPI without using interrupts?

I am assume the ISR is working in such a way that there can't be an SPI response triggering an interrupt. E.g.

18.5.2 SPSR – SPI Status Register
SPIF: SPI Interrupt Flag
When a serial transfer is complete, the SPIF Flag is set.

Won't cause a problem. and SS is an output,

SPI Slave select will be onfigured as an output, which would otherwise be a cause of SPIF getting set.

Just a thought. How fast are the steppers? Could it drive the stepper drivers with ordinary shift registers (3 pins)?

GB

No. Not the way it is implemented on the arduino framework.

I don't see anything in the SPI behaviour (other than the SPIF related interrupts) which are an inherent problem. Have I missed something?

GB

Grumpy_Mike - it's okay, I've read the Arduino SPI library code.

I understand that it is not suitable for use within an ISR. It blocks waiting (a bit like analogRead). (Sorry if I've wasted your time.)

I still don't see anything which is implicitly an obstacle to using SPI in an ISR. Is that true?

GB

Ok, I think I'm starting to understand what I read now... Just confused me a little - the interrupt is only used when I specifically request an interrupt upon completing of a packet, which I doubt I'll have any interest in.

I don't mind not using the SPI lib, and will generally prefer not to, if for no other reason than code and memory are of prime concern. (For example, I spent some time last night gutting the softserial library to make a simple bit-bang interface to the Sparkfun Serial LCD -- just to avoid the unneeded reading code, and wrap up common functions to avoid littering my code with repetitive literals - I'll share it in the next few days).

FWIW, the chip I'll be interfacing with is the 74HC595D, and the general board will be designed around Brian Schmalz' ED quad: Easy Driver Quad

As for step rate? My current in-dev codebase is capable of exceeding 10kHz step rate for two motors, but the final stuff once the remainder of the logic in won't possibly be able to run faster than 2kHz (estimated) and that's WAY above the actual required speed in-use. (Timelapse, gigapano, etc. positioning)

Given a choice, I don't care to read back anything - I just want to send my commands and keep going. I have to time each pulse already, and if I'm reading responses back - my timing is off based on how the other end is playing.

!c

  • the interrupt is only used when I specifically request an interrupt upon completing of a packet

That's exactly my interpretation too.
The problem with the Arduino SPI library is it blocks, like analogRead.

The only other thing I can think of is losing interrupts, otherwise ...

I think you are "good to go"

GB

[edit]PS. Thanks for the link to Easy Driver Quad it looks very interesting.[/edit]

Yes guys sorry, my eyes saw SPI and my mind said I2C, you are quite right SPI is OK.

Cool deal. I have some concerns about how long it might take to send the SPI and thus how much time might be lost the next time I read millis() (but, hopefully, no more than a few ms =)

It may not be a big deal, if I can do SPI2X, that would mean one byte would take 16 clock cycles, and whatever it takes to handle the transition through SPDR.

My ISR, in general, already takes more than 16 clock cycles (no kidding, really? :stuck_out_tongue: ) so what's 16 more to save 5 pins!

!c

(but, hopefully, no more than a few ms =)

SPI has a shift register clocked by the system clock so you are looking more at the uS than the mS.

I read table "28.6 SPI Timing Characteristics", Note 1, for anything over 12MHz, the clock must be divided by 3 or more, so it looks like clock/4. So no need for SPI2X. That is still very fast, though at 4 MHz, or 2uS for 8 bits + setup.

If clock cycles are very critical, or you need to allow interrupts, then I think, with some extra effort, you might be able to 'pipeline' and 'hide' the delay. 'Preload' the shift registers, then load the shifted value into the output register synchronously with the clock. There is a bit to say the register has been emptied, so it can be safe. This suggestion assumes that each shift register is for a different stepper, or the steppers on the same shift register are 'synchronised', though.

[edit]Personally, I would avoid the sort of complexity involved in pipelining if I could.[/edit]
HTH
GB

... I guess I should have been more clear - when motion is active, the ISR will be firing roughly once every 200-400uS, and if it takes 20uS to complete/return, then during runtime, I should expect the timer0 to skew off as much as 100uS every mS. Not a terrible deal, was just thinking out loud really =)

Either way, it looks like it should work, and even at a loss of time like that (if one considers it running for several hours), it should be functional for the vast majority of what I'm doing.

Thanks for the clarification guys, looks like I'll continue to pursue SPI for this purpose!

!c

if it takes 20uS to complete/return

That is quite a long ISR. The SPI should only be a couple of uSec.

I should expect the timer0 to skew off as much as 100uS every mS.

Timer0 shouldn't skew.
The TIMER0_OVF_vect interrupt doesn't adjust the timer at all.
All it does is accumulate the count of millis, so the count of millis will be wrong if interrupts are lost, but timer0 just continues to count accurately.

HTH
GB

That is quite a long ISR. The SPI should only be a couple of uSec.

Unfortunately, making the determination as to whether to step one of four motors at a given point, with a velocity up/down ramp when each motor is moving a different distance requires a number of instructions. You could say "the ramp up is efficient, but ramping them down is much less so."

I'm working on more efficient algorithms, but that takes time - and I presume that I'm going to end up with a lot more than a "few" assembly instructions when done - even with pre-planning.

The TIMER0_OVF_vect interrupt doesn't adjust the timer at all.
All it does is accumulate the count of millis, so the count of millis will be wrong if interrupts are lost, but timer0 just continues to count accurately.

Thanks for that clarification, but seriously, won't the millis count be reduced if I was spending, for some period, up to 5% of the instruction time in an ISR (when summed - assume that my ISR will be called, at fastest rate, every 200uS).

!c

but seriously, won't the millis count be reduced if I was spending, for some period, up to 5% of the instruction time in an ISR

Absolutely agree. The value returned by millis will drift, it will change slower. But that isn't what was written:

I should expect the timer0 to skew off as much as 100uS every mS

Timer0 will not skew, and millis is only updated every 1.024 mS-ish.

If you are really relying on Timer0 as a source of millis, then try to make the ISR's small, and use a few flags to communicate to the 'main loop'.

Unfortunately, making the determination as to whether to step one of four motors at a given point, with a velocity up/down ramp when each motor is moving a different distance requires a number of instructions.

The folks I know who are driving steppers at close to optimal speed and acceleration do most of the calculation off-line, and encapsulate the results in tables embedded in the code. The calculation at run-time may then become relatively efficient, e.g. linear interpolation.

If the main loop rate is pretty high, then it should be accurate enough, especially as you have a buffer between sending the new stepper driver set up, and actually switching the stepper driver to that state.

HTH
GB

I suspect my grammar there caused some confusion - wasn't in any way disagreeing with you about the mechanism, just making sure I understood the end-result was the the same.

I have not modified the behavior of millis(), I just perhaps mis-read the docs that says Timer0 overflow is used for millis() updating. Or perhaps my reading of the various references is simplistic and not mapping to the particulars of how that's happening. Things like this Google Code Archive - Long-term storage for Google Code Project Hosting. lead me to believe that I'll have an issue related to it.

It seems we got a little caught up in the Timer0 thing, which wasn't my intention -- "but seriously" should've been better written as "as an end result".

Either way, it's of little concern outside of whether or not using SPI inside and ISR will "hurt" anything else =)

The folks I know who are driving steppers at close to optimal speed and acceleration do most of the calculation off-line, and encapsulate the results in tables embedded in the code. The calculation at run-time may then become relatively efficient, e.g. linear interpolation.

Generally speaking, I'd like to do this - but since this is a "generic engine", I have to take the user input as to what parameters work best for their motors. So, in this case the "planning" happens right before the motors start, so as little of it is done in the ISR, just basic checks if a planned point of change has occurred, and how far down the change we currently are.

!c

I think you've understood Timer0. The timer continues to count accurately, but the values derived from it and used in, millis, micros(), and delay() will 'loose' 1000 uS, or 1mS every time the Timer0 overflow interrupt is lost. Timer0 is never updated by its ISR, it is all handled in hardware. After the Arduino frameworks setup, Timer0 is only read.

... but since this is a "generic engine", I have to take the user input as to what parameters work best for their motors

Interesting. Good luck.