One of the most important things when learning to program microcontrollers is to not use software delay functions.
You probably have a lot of long _delay() functions for your stepper motor timing. Such is typical "arduino" programming for beginners. It's easy to get started, and blink a few LED's, but it won't get you very far.
Another very important thing is that ISR functions should be very short. Typically 100instructions, and they should execute in less then a micro second. The reason for this is that while an ISR is active, your microcontroller can not do anything else. (Yes, I know, the peripherals keep running on their own, and it is now becoming more common that microcontrollers have an ISR controller with multiple ISR priority levels).
Translated to you stepper motor problem, a good way to do it is to dedicate a timer peripheral to your stepper motor(s). When the timer runs out, it triggers and ISR, the ISR does
A single step and then programs the timer with a new delay for when to do the next step, and it returns. You can have a look at the open source GRBL hal project for how they do it. GRBL forks have come together again in it's own HAL (Hardware Abstraction Layer) project, and it supports many different uC families.
And the more you can put in main routines, and the shorter you can keep your ISR's, the more performance you can squeeze out of the microcontroller you use. A long time ago (25+ years, before GRBL existed) I wrote my own stepper motor driver library. and it worked like this:
- A PC breaks down G code into little sections (arcs' into lines, lines into accelleration, cruse and decelleration sections).
- PC sends these short commands to a microcontroller, which puts them in a circular buffer.
- The micro controller's main program breaks these commands down into timing parameters for individual stepper motors, and puts these as commands in another circular list.
- This last list is read by an ISR. It outputs steps to a motor, reads the next delay from the list, programs the timer and returns.
This is a quite simple idea, and even more "advanced" then GRBL uses currently. The ISR does not do any calculations at all. It just copies step and direction data from the circular buffer to the output ports, and the timer value to the timer peripheral. It was compiled down to about 40 asm opcodes, and that is inclusive the long push and pop of the many registers the AVR's do (AVR's have 32 general purpose registers, and GCC likes to push / pop a lot of them for ISR's). Back then, you simply could not call a single function from within an ISR, because the penalty was a push and pop of nearly all 32 registers. GCC may have better optimisation now, but back then it was absolutely amazing there was even an (open source) C compiler available to program your microcontroller.
The main task for this microcontroller was to keep the circular buffers filled, synchronizing data transfers with the PC and such.
The above technique can be used in almost any microcontroller. More advanced controllers these days can do timed DMA transfers, so you do not even have to use ISR's for the steps at all. But that is a bit too advanced even for me.
So to recoup, the main things for you is:
- Do not use software delay loops. They are tempting because they are easy to use, but they waste time.
- Keep ISR's short, to make room for another ISR as fast as possible. Keep them below 20 or so lines of code, and no loops or waiting for other things in ISR's
Applying these rules will need a change in the way you think about programming. It's also the main reason I dislike "arduino". "arduino" teaches people bad habits. It's enough to blink a LED, but if you want more out of your uC, you'll have to unlearn a lot of what you learned in the first place.