Maybe not an answer that you want to hear but totally an answer that fits the problem.
The better solution is proper programming. The Arduino suite and its HAL is easy to use for one task, but sometimes quite bad for multiple tasks. The solution would be to do things on your own and skip the HAL(hence some libaries).
I will show some basic analysis and a possible solution to communicate some basic concepts, but no C-Code implementation.
Basic analysis
- The Arduino Nano is nothing more than a ATMega328 on a blue PCB with an USB Interface and a bootloader. This controller has multiple Timers and other peripherials like SPI - controlling an Stepper Motor and some LEDs is a task that this controller can handle easily.
- You have two tasks, that you want to have in "parallel":
- Stepping your Motor
- Pulsing your WS2812B LEDs
- The way you control your Stepper Motor is through a A4988 driver. This driver has a Step/Dir Interface. Means that with one pin(Dir) the rotation direction is controlled(CW/CCW), the other pin(Step) tells the controller to do one step.
- According to the Datasheet the WS2812B LED are controlled by a 24 Bit value, divided in 3 times 8 bit, each 8 Bit(=one Byte) for in color(RGB). A dataframe is indicated by a reset period(50µs) before these 24 Bit. If one bit is an 1 or a 0 is indicated by the pulsewidth of a squarewave of a period of 1,25µs.
- According to the datasheet the ATMega328 has 2 8 Bit timers and one 16 Bit timer and several PWM Channels.
Possible solution
Outsource the stepping of the Motor to an 8 Bit Timer and the WS2812 Timing to an 8 Bit or 16 Bit PWM.
- As stated before the motor just needs to get something like a clock in the step pin(StepCLK). This can easily be done with a hardware timer. For example with Timer0 in Normal Mode or for even finer frequency resolution in CTC Mode. In this mode you dont have to do anything in code for generating the StepCLK waveform if you enable the pin toggle on compare match - this wont waste any CPU time so your controller can handle other things. Look at the datasheet in section 14.7.2 for further information.
- For the LEDs you can use Timer1 in PWM Mode. Here you have to setup the timer first to get the bit period of 1,25µs, after that you just have to set the compare value of the PWM timer. A good way to do this is to set this timer to fastPWM mode. In this case OC1A or OC1B will can set high in the beginning of a PWM period and set low on a compare match. in this case the only thing the CPU has to do is to set the period for each PWM cycle, which can be done in the Timer1 overflow interrupt routine. To see your timing tolerances see this wordpress article.
- For you application you can do some things in the main loop. From there the only thing you have to do is write to the Timer0 compare register for acceleration ramps and update a LED-RGB buffer for your LED display. Probably the rest of the time the CPU can go to sleep mode for energy effenciency.
Pseudo Code
For better clarification I will provide some c-ish pseudocode:
volatile uint8_t LEDBuffer[LEDNum*3]; //each color value has its own array field.
volatile ledIdx,bitIdx;
Timer1OverflowInterrupt(){
if(bitIdx % 8 == 0) ledIdx++; //increments ledindex after 8 bits are masked
if(LEDBuffer[ledIdx] & (1<<(bitIdx%8) != 0){ //With bitIdx we shift through all bits of the current LEDBuffer value and check if its zero or one
timer1Compare = ONEBitPERIOD
}else{
timer1Compare = ZEROBitPERIOD
}
bitIdx++
if(ledIdx==LedNum){
ledIdx=0; //resets ledindex if array size is reached
timer1Compare = RESETbitPERIOD //after we shifted all LED values out we indicate a break for reset-synchronization
}
}
setup(){
configureTimer0asCTCwithOutputCompare()
configureTimer1asFastPWMwithOutputCompareAndOverflowInterruptEnable()
setStepDirPin()
}
loop(){
for(uint8_t i = 0; i<ledNum;i++){
LEDBuffer[i*3] = redValue;
LEDBuffer[i*3+1] = greenValue;
LEDBuffer[i*3+2] = blueValue;
}
timer0compare = stepperSpeedValue
}
Explanation and further problem analysis
This solution makes use of the hardware timers. This means there are actual hardware registers which are counting with their own clock and do actions based on some configuration registers set by the application program. Only if a special event occurs the CPU is notified with a interrupt(hence the name interrupt) and shortly switches to another piece of code(like Timer1OverflowInterrupt) after that resuming its main program(loop).
In contrast and for example The fast LED libary from Adafruit makes heavy use of assembler nops(no operation), which means the CPU is blocked by doing nothing.
I hope that helps you in the understanding and basic concurrent programming on embedded systems. You have to figure out by yourself how to configure the Timers but I think I gave you some good hints how to do it.